TypeOfNaN

Fix the "Function makes the dependencies of useEffect Hook change on every render" warning in React

Nick Scialli
November 26, 2021

If you’re working with React hooks and, in particular, the useEffect hook, you have likely encountered the following warning:

The ‘functionName’ function makes the dependencies of useEffect Hook (at line X) change on every render. Move it inside the useEffect callback. Alternatively, wrap the definition of ‘functionName’ in its own useCallback() Hook. (react-hooks/exhaustive-deps)

So what’s going on here and how do we fix it?

A contrived example

Let’s throw together a quick, contrived example for the purpose of this blog post. We’ll have a simple count state. Every time count changes, we want to run an effect that logs the count to the console. So we write the following code:

function App() {
  const [count, setCount] = useState(0);

  const logCount = () => {
    console.log(count);
  };

  useEffect(() => {
    logCount();
  }, [logCount]);

  return <div>{count}</div>;
}

And now we see the warning! Now that we have a simple example, lets use it to explore what’s happening.

Dependency arrays and referential equality

In our example, our useEffect hook has the logCount function in its dependency array. That means the effect will run every time logCount changes.

You might be thinking that logCount doesn’t really change—it’s the same function each render. But we have to remember that JavaScript equality works based on referential equality. That is, objects are only equal to each other if they reference the same object in memory. That’s why we end up with something like this:

console.log({ name: 'Donna' } === { name: 'Donna' });
// false

Despite having the same key/value pairs, these two objects live separately in memory and are therefore not equal.

This works the same way for functions:

const fn1 = () => 'Donna';
const fn2 = () => 'Donna';

console.log(fn1 === fn2);
// false

And this is what’s happening in our useEffect dependency array. The logCount function is recreated on each component render and therefore will always be different than the logCount function created during the previous render.

How do we fix this?

Well, first I’d like to point out that the warning message is actually pretty helpful: it gives us the two solutions that will help us fix the problem:

  1. Move logCount inside the useEffect hook
  2. Wrap logCount in a useCallback hook

I’ll show you how to accomplish both here.

Moving logCount inside the useEffect hook

This one is fairly straightforward: we take the logCount function and move it inside the useEffect hook. Since logCount is now inside the useEffect hook, we no longer need it in the dependency array. However, since logCount depends on the count variable, we’ll have to add the count variable to the dependency array.

Our fixed code now looks like this:

function App() {
  const [count, setCount] = useState(1);

  useEffect(() => {
    const logCount = () => {
      console.log(count);
    };
    logCount();
  }, [count]);

  return <div className="App">foo</div>;
}

And the warning is gone!

Wrapping logCount in a useCallback hook

You may not want, or be able, to put logCount inside the useEffect hook. For example, if you need logCount elsewhere in the component, putting it inside the effect could cause it to be undefined elsewhere.

The solution when this is the case is to wrap the logCount function definition in a useCallback hook. What this does is returns a memoized function whose reference will only change if something in the hook’s dependency array changes.

Let’s take a look at the correct implmentation and then I’ll describe what’s going on in detail:

import { useState, useEffect, useCallback } from 'react';

function App() {
  const [count, setCount] = useState(1);

  const logCount = useCallback(() => {
    console.log(count);
  }, [count]);

  useEffect(() => {
    logCount();
  }, [logCount]);

  return <div className="App">foo</div>;
}

Now that we have wrapped our logCount function in a useCallback hook, it will maintain the same memory reference through each render—unless something in its dependency array changes. In this case, we added count to the dependency array. We need the function to update when count updates or our effect won’t run.

Conclusion

React dependency array issues can be pretty tricky. Fortunately, there’s always a solution. In some cases, the warnings you get even have helpful solutions in them!

If you'd like to support this blog by buying me a coffee I'd really appreciate it!

Nick Scialli

Nick Scialli is a senior UI engineer at Microsoft.

© 2024 Nick Scialli