DEV Community

Alexander Kim
Alexander Kim

Posted on

6 1

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

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more