Source

src/hooks/useLandmarks.ts

// This file houses crud methods to interact with landmarks on the server. They are generally called by react query mutations.

import axios, { AxiosRequestConfig } from "axios"
import { Region } from "react-native-maps";
import { QueryClient, useMutation, useQuery, useQueryClient } from "react-query";
import { API_URL, reportAxiosError as reportAxiosError } from "../globals"
import { authStore } from "../stores/AuthStore";
import { useAuth } from "./useAuth";

/**
 * Interface representing a landmark's comment.
 */
export interface LMComment {
    /**
     * The comment's id.
     */
    id: string,
    /**
     * The id of the user who posted the comment
     */
    poster: string,
    /**
     * The text content of the comment
     */
    text: string
    /**
     * The landmark that this comment is associated with.
     */
    landmark: Landmark
}

/**
 * Interface representing a landmark object
 */
export interface Landmark {
    /**
     * The id of the landmark.
     */
    id?: string | null,
    /**
     * The rating of the landmark.
     */
    rating?: number | null,
    /**
     * The id of the user who created the landmark.
     */
    user?: string | null,
    /**
     * The longitude of the landmark's location.
     */
    longitude?: number | null,
    /**
     * The latitude of the landmark's location.
     */
    latitude?: number | null, 
    /**
     * The landmark's title.
     */
    title?: string | null,
    /**
     * The landmark's description.
     */
    description?: string | null,
    /**
     * User [comments]{@link LMComment} associated with this landmark.
     */
    comments?: LMComment[] | null,
    /**
     * An integer representing the type of this landmark.
     */
    landmark_type?: number | null, // for working with existing database schema, should be changed
    /**
     * A Date object representing when this landmark was created.
     */
    time?: Date | null
}

/**
 * A custom hook containing [react-query]{@link https://react-query.tanstack.com/} queries and mutations and other logic related to interacting with {@link Landmark} objects.
 * @category Hooks
 * @namespace useLandmarks
 */
export const useLandmarks = (region: Region | undefined) => {
    const { refreshAccessToken } = useAuth();

    /**
     * The local instance of the [react-query QueryClient]{@link https://react-query.tanstack.com/reference/QueryClient#_top}.
     * @memberOf useLandmarks
     */
    let queryClient: QueryClient;

    try {
        queryClient = useQueryClient()
    } catch (error) {
        console.log("Something went wrong when retrieving query client:")
        console.log(error)
    }

    /**
     * The callback responsible for retrieving {@link Landmark} from the API, used by the [react-query useQuery]{@link https://react-query.tanstack.com/reference/useQuery#_top} hook.
     * * @memberOf useLandmarks
     */
    const getLandmarks = async (region: Region | undefined) => {
        // if (region) {
            const config: AxiosRequestConfig = {
            method: 'GET',
            // url: `${API_URL}/api/landmarks/?lat=${region.latitude}&long=${region.longitude}&lat_delta=${region.latitudeDelta}&long_delta=${region.longitudeDelta}`,
            url: `${API_URL}/api/landmarks/`,
            headers: { "Authorization": "Bearer " + authStore.accessToken, }
        } 

        try {
            const response = await axios(config);   
            return response.data;
        } catch (error) {
            if (error.response.status == 401) {
                try {
                    await refreshAccessToken()    
                    const response = await axios({...config, headers: { "Authorization": "Bearer " + authStore.accessToken }});   
                    return response.data;
                } catch (error) {
                    // refreshAccessToken will report errors
                }
            }

            reportAxiosError('Something went wrong when retrieving landmarks', error)
            throw new Error;
        }   
        // }
        // else {
        //     return [];
        // }
    }

     /**
     * The callback responsible for adding a new {@link Landmark} to the server, used by the [react-query useMutation]{@link https://react-query.tanstack.com/reference/useMutation#_top} hook.
     * * @memberOf useLandmarks
     */
    const createLandmark = async (landmarkValue: Landmark | undefined): Promise<Landmark | undefined> => {
        const config: AxiosRequestConfig = {
            method: 'POST',
            data: landmarkValue,
            url: API_URL + `/api/landmark/`,
            headers: { "Authorization": "Bearer " + authStore.accessToken, }
        } 

        try {
            const response = await axios(config);   
            return response.data;
        } catch (error) {
            if (error.response.status == 401) {
                try {
                    await refreshAccessToken()    
                    const response = await axios({...config, headers: { "Authorization": "Bearer " + authStore.accessToken }});   

                    return response.data;
                } catch (error) {
                    // refreshAccessToken will report errors
                }
            }

            reportAxiosError('Something went wrong when retrieving landmarks', error)
            throw new Error;
        }
    }

    /**
     * The callback responsible for updating a {@link Landmark} on the server, used by the [react-query useMutation]{@link https://react-query.tanstack.com/reference/useMutation#_top} hook.
     * * @memberOf useLandmarks
     */
    const editLandmark =  async (landmarkValue: Landmark) => {
        const config: AxiosRequestConfig = {
            method: 'PUT',
            data: landmarkValue,
            url: API_URL + `/api/landmark/`,
            headers: { "Authorization": "Bearer " + authStore.accessToken, }
        } 

        console.log(landmarkValue)

        try {
            const response = await axios(config);   
            return response.data;
        } catch (error) {
            if (error.response.status == 401) {
                try {
                    await refreshAccessToken()    
                    const response = await axios({...config, headers: { "Authorization": "Bearer " + authStore.accessToken }});   

                    return response.data;
                } catch (error) {
                    // refreshAccessToken will report errors
                }
            }

            reportAxiosError('Something went wrong when retrieving landmarks', error)
            throw new Error;
        }
    }

    /**
     * The callback responsible for deleting a {@link Landmark} from the server, used by the [react-query useMutation]{@link https://react-query.tanstack.com/reference/useMutation#_top} hook.
     * * @memberOf useLandmarks
     */
    const removeLandmark =  async (id?: string | null) => {
        const config: AxiosRequestConfig = {
            method: 'DELETE',
            url: API_URL + `/api/landmark/${id}`,
            headers: { "Authorization": "Bearer " + authStore.accessToken, }
        } 
        try {
            const response = await axios(config);   
            return response.data;
        } catch (error) {
            if (error.response.status == 401) {
                try {
                    await refreshAccessToken()    

                    // add new access token to header
                    const response = await axios({...config, headers: {"Authorization": "Bearer " + authStore.accessToken}});   

                    return response.data;
                } catch (error) {
                    // refreshAccessToken will report errors
                }
            }

            reportAxiosError('Something went wrong when retrieving landmarks', error)
            throw new Error;
        }
    }

    // get-all query
    const { data: landmarks, status: getLandmarksStatus, refetch: refetchLandmarks } = useQuery<Landmark[], Error>(['getLandmarks', region], () => getLandmarks(region), {
        placeholderData: () => queryClient.getQueryData('getLandmarks'),
        staleTime: 1000,
        refetchInterval: 30000,
        refetchOnReconnect: true,
        refetchOnMount: false
    })

    // mutations
    const { status: addLandmarkStatus, mutateAsync: addLandmarkAsync, reset: resetAddLm, data: newLandmark } = useMutation(createLandmark, {
        onSuccess: data => {
            queryClient.invalidateQueries('getLandmarks')
        },  
    })

    const { status: updateLandmarkStatus, mutateAsync: updateLandmark, reset: resetUpdateLm } = useMutation(editLandmark, {
        onSuccess: () => queryClient.invalidateQueries('getLandmarks'),  
        onError: () => queryClient.invalidateQueries('getLandmarks'),  
    })

    const { status: deleteLandmarkStatus, mutateAsync: deleteLandmark, reset: resetDeleteLm } = useMutation(removeLandmark, {
        onSuccess: () => queryClient.invalidateQueries('getLandmarks'),  
        onError: () => queryClient.invalidateQueries('getLandmarks'),  
    })

    return { 
        landmarks, getLandmarksStatus, refetchLandmarks,  //reading
        addLandmarkAsync, resetAddLm, addLandmarkStatus, newLandmark, // creating
        updateLandmark, resetUpdateLm, updateLandmarkStatus, // updating
        deleteLandmark, resetDeleteLm, deleteLandmarkStatus, // deleting
    }
}