reactjseslintmemoization

Rule react-hooks/exhaustive-deps and over-optimization with useCallback


There are a lot of post about the eslint rule react-hooks/exhaustive-deps, and also about the good usage of the memoizations hooks useMemo and useCallback which are generally way overused, but I haven't found any good answers to the following question:

How can I mix the policy of not doing over-optimization by using useMemo and useCallback in the early stage of the development of an application, and still follow the react-hooks/exhaustive-deps rule?

For example if I have a useEffect that will execute a function when a value changes:

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

But here eslint will tells me that I need to pass myFunction to the dependency array, which may lead to an infinite loop except if I put myFunction into a useCallback with value in the dependency array (and myFunction in the one of the useEffect).

I'm not sure what is the best practice here, I wanted to avoid using memoization for such simple functions because useCallback can be actually quite demanding in terms of performance.

I've made some research about one topic and the other, but I didn't find any useful information concerning both.

I've seen this answer that explains when not to use useEffect, but I think there still are some use cases where the use of a function defined in a component is needed inside a useEffect callback.


Solution

  • Edit

    Ultimately Eslint is just a dumb style checker. Writing your entire code strictly following eslint rules will only lead to bad underperforming code. Its all about finding what exactly suites your use case:

    1. The recommended way is to have the function inside the useEffect block / use memoization. Ultimately why is eslint forcing to have function as depdendency? - Because people make mistakes, forget dependencies. Having the rule set is better than having people forget few ones.

    2. If you feel all your dependencies are covered, ignore eslint, no need to add the function to the dependency list.


    In general it is highly recommended to keep the react-hooks/exhaustive-deps rule on. It is only safe to omit a function from the dependency list if nothing in it (or the functions called by it) references props, state, or values derived from them. In case of functions there are basically two ways to avoid the warning.

    1. You can move the function inside useEffect:
    function DependencyTest({ value }) {
      useEffect(() => {
        const myFunction = () => value;
        myFunction();
      }, [value]);
    }
    
    1. useCallback to memoize the function and add it to dependencies:
    function DependencyTest({ value }) {
      const myFunction = useCallback(() => value, [value]);
    
      useEffect(() => {
        myFunction();
      }, [myFunction]);
    }
    

    In order to avoid performance issues I would go with the first one. The same thing is referred to in the react docs: https://legacy.reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies