Custom react hook causes infinite loop

Viewed 2060

I have a custom fetch hook:

export const useFetch = (url: string) => {
  const [response, setResponse] = useState<any>(null);
  const [error, setError] = useState<any>(null);

  const fetchList = (url: string) => {
    return API.get(AMPLIFY_ENPOINTS.default, url, { response: true });
  };

  useEffect(() => {
    const fetchFunc = async () => {
      try {
        const fetchResponse = await fetchList(url);
        setResponse(fetchResponse.data);
      } catch (error) {
        setError(error);
      }
    };
    fetchFunc();
  }, [url]);

  return { response, error };
};

This I then use in a component:

const fetchOrders = useFetch(apiUrl);
useEffect(() => {
  const { response, error } = fetchOrders;
  if (error) setError(error);
  if (response) {...}
}, [fetchOrders]);

And this causes an infinite loop, how should I go about fixing it?

3 Answers

The fetchOrder reference keeps changes on each re-render since you are returning a newly created object each time and hence it triggers an infinite loop when you call setError within your useEffect.

Instead of adding fetchOrders as a dependency, add response and error separately

const { response, error } = useFetch(apiUrl);
useEffect(() => {
  if (error) setError(error);
  if (response) {...}
}, [response, error]);

It is this line that is responsible for this

return { response, error };

And of course his partner in crime is this [fetchOrders] dependency array in the second useEffect.

The first line returns a new object and thus fetchOrders is always a new value.

After thinking about your code, I came up to conclude that you probably don't need the second useEffect at all (I think).

// the useFetch only re-fetches when url changes
const { response, error } = useFetch(url);

// use response directly, you don't need to re-set the error nor the response in your component.

But if you were to do a side effect with response, you can consider upgrading your useFetch hook to accept a callback parameter and useEffect with a response dependency.

In your component, try wrapping the input in useMemo.

const url = useMemo(() => apiUrl, [])
const fetchOrders = useFetch(url);
useEffect(() => {
  const { response, error } = fetchOrders;
  if (error) setError(error);
  if (response) {...}
}, [fetchOrders]);

Otherwise, apiUrl is a local variable and gets recreated every time the functional component runs, causing the infinite loop.

Related