DEV Community

Amit K.। 🎧
Amit K.। 🎧

Posted on

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

If you work with 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 check a quick example. We’ll have a simple react app and updateCount function which we will call inside useEffect hooks and we need to pass the function name in dependency array or it will show React Hook useEffect has missing dependency warning. What we want here is 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 updateCount = () => {
    console.log(count);
  };

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

  return <div>{count}</div>;
}
Enter fullscreen mode Exit fullscreen mode

And now we see the warning! So, lets use it to explore what’s happening.

Dependency arrays and referential equality

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

You might be thinking that updateCount is a function and it doesn’t really changes after each render. But we have to remember that JavaScript equality works based on referential equality. According to the principle of Object.is(), any two objects pointing to the exact memory location are considered equal or only equal to each other if they reference the same object in memory. That’s why when we compare two objects with same key and value we end up with something like this:

console.log({ name: 'foo' } === { name: 'foo' });
// false
Enter fullscreen mode Exit fullscreen mode

These two objects live separately in memory and are therefore not equal.

This works the same way for functions:

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

console.log(fn1 === fn2);
// false
Enter fullscreen mode Exit fullscreen mode

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

How to fix this?

Well, the warning message is actually pretty helpful and itself gives us the solutions that will help us fix the problem:

  • Move updateCount inside the useEffect hook
  • Wrap updateCount in a useCallback hook

I’ll show you both solution here.

Moving updateCount inside the useEffect hook
This one is fairly straightforward: we take the updateCount function and move it inside the useEffect hook. Now the updateCount is now inside the useEffect hook, we no longer need it in the dependency array. However, since updateCount 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 updateCount = () => {
      console.log(count);
    };
    updateCount();
  }, [count]);

  return <div className="App">{count}</div>;
}
Enter fullscreen mode Exit fullscreen mode

And the warning is gone!

Wrapping updateCount in a useCallback hook
Let's consider a situation in which you need updateCount to call elsewhere in the component, putting it inside the effect could cause it to be undefined elsewhere.

For this solution we need to wrap the updateCount 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 implementation:

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

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

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

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

  return <div className="App">{count}</div>;
}
Enter fullscreen mode Exit fullscreen mode

Now that we have wrapped our updateCount 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. But, there’s always a solution!

Oldest comments (1)

Collapse
 
brense profile image
Rense Bakker

Keep in mind though, you should never use the useEffect hook to update state, unless its in response to something that happens outside of React scope. For example an API request or some event listener (and in both those cases you need a cleanup function as well). Otherwise you run the risk of creating an infinite render loop, or at the very least, cause redudant rerenders, making your app feel slow.

In your example, if you change your updateCount to actually update the count:

const updateCount = useCallback(() => {
  setCount(count++)
}, [count])
Enter fullscreen mode Exit fullscreen mode

It will actually cause an infinite render loop. You can still fix it by doing this:

count updateCount = useCallback(() => {
  setCount(count => count += 1)
}, []) // no dependencies!
Enter fullscreen mode Exit fullscreen mode

But it's an indicator that you're doing something that you shouldnt be doing!

Usually when you have the need to update state in response to something in React scope, the hook you want is useMemo. This is called derived state (state that can be derived from other state or props).