Dusty
Dusty

Reputation: 364

useApi hook with multiple parameters

I have a useApi custom hook, that takes an endpoint (url) with multiple parameters. When one of the parameters changes a graph is rendered. The problem is that when one parameter changes, another parameter is changing as well and the graph is rendered twice. How can I solve it? Thanks

  const useApi = (endpoint, requestType, body) => {
    const [data, setData] = useState({ fetchedData: [], isError: false, isFetchingData: false });
    useEffect(() => {
        requestApi();
    }, [endpoint]);
    const requestApi = async () => {
        let response = {};
        try {
            setData({ ...data, isFetchingData: true });
            console.log(endpoint);
            switch (requestType) {
                case 'GET':
                    return (response = await axios.get(endpoint));
                case 'POST':
                    return (response = await axios.post(endpoint, body));
                case 'DELETE':
                    return (response = await axios.delete(endpoint));
                case 'UPDATE':
                    return (response = await axios.put(endpoint, body));
                case 'PATCH':
                    return (response = await axios.patch(endpoint, body));
                default:
                    return (response = await axios.get(endpoint));
            }
        } catch (e) {
            console.error(e);
            setData({ ...data, isError: true });
        } finally {
            if (response.data) {
                setData({ ...data, isFetchingData: false, fetchedData: response.data.mainData });

            }
        }
    };
    return data;
};

Upvotes: 1

Views: 1268

Answers (1)

alioguzhan
alioguzhan

Reputation: 7917

There are a couple of places that can be refactored:

First, you can get rid of data dependency in your useEffect by converting it to something like this:

setData(currentData => {
  return { ...currentData, isFetchingData: true }
})

As second and most importantly, you should either move your requestApi function to inside of the useEffect or wrap it with a useCallback function.

And finally, It is totally OK if there are multiple renders followed by another. Because you are depending on all params inside of your useEffect.

One thing that you can do is canceling the axios requests during the unmount by taking advantage of returning a function in useEffect.

So here is the final version of your code:

const useApi = (endpoint, requestType, body) => {
  const [data, setData] = useState({
    fetchedData: [],
    isError: false,
    isFetchingData: false
  })
  useEffect(() => {
    let axiosSource = axios.CancelToken.source() // generate a source for axios
    let didCancel = false // we can rely on this variable.
    const requestApi = async () => {
      let response = {}
      try {
        setData(data => {
          return { ...data, isFetchingData: true }
        })
        console.log(endpoint)
        const axiosOptions = { cancelToken: axiosSource.token }
        switch (requestType) {
          case 'GET':
            return (response = await axios.get(endpoint, axiosOptions))
          case 'POST':
            return (response = await axios.post(endpoint, body, axiosOptions))
          case 'DELETE':
            return (response = await axios.delete(endpoint, axiosOptions))
          case 'UPDATE':
            return (response = await axios.put(endpoint, body, axiosOptions))
          case 'PATCH':
            return (response = await axios.patch(endpoint, body, axiosOptions))
          default:
            return (response = await axios.get(endpoint, axiosOptions))
        }
      } catch (e) {
        console.error(e)
        if (!didCancel) {
          setData(data => {
            return { ...data, isError: true }
          })
        }
      } finally {
        // do not update the data if the request is cancelled
        if (response.data && !didCancel) {
          setData(data => {
            return {
              ...data,
              isFetchingData: false,
              fetchedData: response.data.mainData
            }
          })
        }
      }
    }
    requestApi()
    // Here we are saying to axios cancel all current ongoing requests
    // since this is the cleanup time.
    return () => {
      didCancel = true
      axiosSource.cancel()
    }
  }, [body, endpoint, requestType])
  return data
}

I did not test the code. But it should be working. Please try and tell me what happened.

Upvotes: 1

Related Questions