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:
- Move
logCount
inside theuseEffect
hook - Wrap
logCount
in auseCallback
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!
Nick Scialli is a senior UI engineer at Microsoft.