How to deal with exhaustive-deps when a required dependency must not trigger a call on update?

Viewed 47

In my functional React component, I have a function that is used both on component mount and in some child event:

const [count, setCount] = useState(0);
  
const myFunction = useCallback(() => {
    // do something with count, such as a fetch or console.log for example
}, [])

useEffect(() => {   
    myFunction()
}, [myFunction])

return (
    <button onClick={myFunction}>
        myButton
    </button>
)

myFunction must be called on mount and when the button is clicked. However, the classic React way of doing this implies to declare count as a dependency in useCallback, which would trigger a call of myFunction each time count is updated by another part of the program, which I do not want. count must be able to be updated without triggering myFunction.

I cannot find a clean solution for doing this. Is there one? Or should I write my code a different way? What's the best way to deal with this situation?

3 Answers

For your specific example, there's a simple solution. Instead of using the count state variable inside of useCallback, and therefore making it a dependency of the useCallback hook, you could just use the callback parameter of the state updater function. So instead of:

const myFunction = useCallback(() => {
    setCount(count + 1)
}, [])

You could write it like this:

const myFunction = useCallback(() => {
    setCount((prevCount) => prevCount + 1)
}, [])

Another way, for more complex instances, would be to use the useReducer hook instead of useState. Since you would be using useReducer's dispatch function to update the state, there wouldn't any need to have the state as a dependency in the useCallback hook.

const [count, dispatch] = useReducer((state, action) => {
  switch (action.type) {
    case "INCREMENT":
      return state + action.by;

    default:
      return state;
  }
}, 0);

const myFunction = useCallback(() => {
  dispatch({ type: "INCREMENT", by: 1 });
}, []);

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

return (
  <div>
    <p>{count}</p>
    <button onClick={myFunction}>myButton</button>
  </div>
);

Use React functional updates for setState:

setCount(count => count + 1);

This way you do not need to add count to dependencies.

As of august 2022, this actually happens to be a flaw in the design of the framework. I went over many posts on the Internet and did not find any solution. The only significant messages that I found were people complaining about this mechanism not being suitable for many legit use cases. I guess there isn't really a standard solution for this problem. Personally, I decided to disable the warning globally on my project, because it is a pain more than it is helpful.

I won't mark my answer as "accepted" because I deem it unsatisfied, but for whomever will read this, I suggest taking a look at the other answers on this post. They do not solve the problem but give useful tricks that might fit your need.

Related