DEV Community

Alexander Kim
Alexander Kim

Posted on

Dealing with infinite loops in useEffect hook

When i switched to hooks from class styled components, i considered useEffect() hook with empty dependencies array as componentDidMount(), what stopped me from using it this way - eslint error react-hooks/exhaustive-deps, so i've started to dig deeper on how to do it the right way.

Let's look at this common example, where we are fetching some data from an API using our custom hook:

const useFetch = <R, B>(
  url: string,
  method: Methods,
  body: B | undefined = undefined,
  headers: { [key: string]: string } = {}
) => {
  const [response, setResponse] = useState<R | null>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    (async () => {
      dispatch(onRequest(true));
      try {
        if (user.jwt) {
          headers['Authorization'] = `Bearer ${user.jwt}`;
        }
        const result = await api<R, B>(url, method, body, headers);
        setResponse(result as R);
      } catch (error) {
        setError(error);
        dispatch(onFailure(false, error));
      } finally {
        dispatch(onFinish(false));
      }
    })();
  }, []);
};
Enter fullscreen mode Exit fullscreen mode

This looks and works as expected, when your component mounts, data would be fetched once, but eslint would start warn you:

"ESLint: React Hook useEffect has missing dependencies: 'body', 'dispatch', 'headers', 'method', 'url', and 'user.jwt'. Either include them or remove the dependency array.(react-hooks/exhaustive-deps)"

If we add all of these noted above dependencies, then we would get an infinite loop, because of the headers param is equal to {}.
In JavaScript {} === {} is always false, so we would get stuck in a loop.

Solution to this problem, is to use useRef() hook:

const { current: hdrs } = useRef(headers);
Enter fullscreen mode Exit fullscreen mode

Then we just have to rename headers param references to hdrs (we just destructured current from useRef for convenience, otherwise we would have to use it as variableName.current). And add all of useEffect() dependencies in to array:

[body, hdrs, dispatch, method, url, user.jwt]
Enter fullscreen mode Exit fullscreen mode

Now, every time our component is mounted, it fires useEffect, but we won't get stuck in a nasty loop, because all of its dependencies remain unchanged.

I previously used useMemo() to save a reference to a value, but there's a useRef() for such purposes. A good article that pointed me in a right direction

Top comments (0)