How to fix missing dependency warning when using useEffect React Hook

632,683

Solution 1

If you aren't using fetchBusinesses method anywhere apart from the effect, you could simply move it into the effect and avoid the warning

useEffect(() => {
    const fetchBusinesses = () => {
       return fetch("theURL", {method: "GET"}
    )
      .then(res => normalizeResponseErrors(res))
      .then(res => {
        return res.json();
      })
      .then(rcvdBusinesses => {
        // some stuff
      })
      .catch(err => {
        // some error handling
      });
  };
  fetchBusinesses();
}, []);

If however you are using fetchBusinesses outside of render, you must note two things

  1. Is there any issue with you not passing fetchBusinesses as a method when it's used during mount with its enclosing closure?
  2. Does your method depend on some variables which it receives from its enclosing closure? This is not the case for you.
  3. On every render, fetchBusinesses will be re-created and hence passing it to useEffect will cause issues. So first you must memoize fetchBusinesses if you were to pass it to the dependency array.

To sum it up I would say that if you are using fetchBusinesses outside of useEffect you can disable the rule using // eslint-disable-next-line react-hooks/exhaustive-deps otherwise you can move the method inside of useEffect

To disable the rule you would write it like

useEffect(() => {
   // other code
   ...

   // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) 

Solution 2

Edit 09/20/2021

There are very good options for state management libs if you are creating a new app or have enough flexibility. Check out Recoil.

Just for completeness:

1. (Stopped working) Use function as useEffect callback

useEffect(fetchBusinesses, [])

2. Declare function inside useEffect()

useEffect(() => {
  function fetchBusinesses() {
    ...
  }
  fetchBusinesses()
}, [])

3. Memoize with useCallback()

In this case, if you have dependencies in your function, you will have to include them in the useCallback dependencies array and this will trigger the useEffect again if the function's params change. Besides, it is a lot of boilerplate... So just pass the function directly to useEffect as in 1. useEffect(fetchBusinesses, []).

const fetchBusinesses = useCallback(() => {
  ...
}, [])
useEffect(() => {
  fetchBusinesses()
}, [fetchBusinesses])

4. Function's default argument

As suggested by https://stackoverflow.com/a/61183162/5552686

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

5. Create a custom hook

Create a custom hook and call it when you need to run function only once. It may be cleaner. You can also return a callback to reset re-run the "initialization" when need.

// customHooks.js
const useInit = (callback, ...args) => {
  const [mounted, setMounted] = useState(false)
  
  const resetInit = () => setMounted(false)

  useEffect(() => {
     if(!mounted) {
        setMounted(true);
        callback(...args);
     }
  },[mounted, callback]);

  return [resetInit]
}

// Component.js
return ({ fetchBusiness, arg1, arg2, requiresRefetch }) => {
  const [resetInit] = useInit(fetchBusiness, arg1, arg2)

  useEffect(() => {
    resetInit()
  }, [requiresRefetch, resetInit]);

6. Disable eslint's warning

Disabling warnings should be your last resort, but when you do, better do it inline and explicitly, because future developers may be confused or create unexpected bugs without knowing linting is off

useEffect(() => {
  fetchBusinesses()
}, []) // eslint-disable-line react-hooks/exhaustive-deps

Original reply

You can set it directly as the useEffect callback:

useEffect(fetchBusinesses, [])

It will trigger only once, so make sure all the function's dependencies are correctly set (same as using componentDidMount/componentWillMount...)

Solution 3

./src/components/BusinessesList.js
Line 51:  React Hook useEffect has a missing dependency: 'fetchBusinesses'.
Either include it or remove the dependency array  react-hooks/exhaustive-deps

It's not a JavaScript/React error, but an ESLint (eslint-plugin-react-hooks) warning.

It's telling you that the hook depends on function fetchBusinesses, so you should pass it as a dependency.

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

It could result in invoking the function on every render if the function is declared in a component like:

const Component = () => {
  /*...*/

  // New function declaration every render
  const fetchBusinesses = () => {
    fetch('/api/businesses/')
      .then(...)
  }

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

  /*...*/
}

because every time the function is redeclared with a new reference.

The correct way of doing this stuff is:

const Component = () => {
  /*...*/

  // Keep the function reference
  const fetchBusinesses = useCallback(() => {
    fetch('/api/businesses/')
      .then(...)
  }, [/* Additional dependencies */])

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

  /*...*/
}

Or just define the function in useEffect.

More: [ESLint] Feedback for 'exhaustive-deps' lint rule #14920

Solution 4

These warnings are very helpful for finding components that do not update consistently: Is it safe to omit functions from the list of dependencies?.

However, if you want to remove the warnings throughout your project, you can add this to your ESLint configuration:

  {
  "plugins": ["react-hooks"],
  "rules": {
    "react-hooks/exhaustive-deps": 0
    }
  }

Solution 5

The solution is also given by React. They advice you use useCallback which will return a memoize version of your function:

The 'fetchBusinesses' function makes the dependencies of the useEffect Hook (at line NN) change on every render. To fix this, wrap the 'fetchBusinesses' definition into its own useCallback() Hook react-hooks/exhaustive-deps

useCallback is simple to use as it has the same signature as useEffect. The difference is that useCallback returns a function. It would look like this:

 const fetchBusinesses = useCallback( () => {
        return fetch("theURL", {method: "GET"}
    )
    .then(() => { /* Some stuff */ })
    .catch(() => { /* Some error handling */ })
  }, [/* deps */])
  // We have a first effect that uses fetchBusinesses
  useEffect(() => {
    // Do things and then fetchBusinesses
    fetchBusinesses();
  }, [fetchBusinesses]);
   // We can have many effects that use fetchBusinesses
  useEffect(() => {
    // Do other things and then fetchBusinesses
    fetchBusinesses();
  }, [fetchBusinesses]);
Share:
632,683
russ
Author by

russ

Updated on July 08, 2022

Comments

  • russ
    russ almost 2 years

    With React 16.8.6 (it was good on previous version 16.8.3), I get this error when I attempt to prevent an infinite loop on a fetch request:

    ./src/components/BusinessesList.js
    Line 51:  React Hook useEffect has a missing dependency: 'fetchBusinesses'.
    Either include it or remove the dependency array  react-hooks/exhaustive-deps
    

    I've been unable to find a solution that stops the infinite loop. I want to stay away from using useReducer(). I did find this discussion [ESLint] Feedback for 'exhaustive-deps' lint rule #14920 where a possible solution is You can always // eslint-disable-next-line react-hooks/exhaustive-deps if you think you know what you're doing. I'm not confident in what I'm doing, so I haven't tried implementing it just yet.

    I have this current setup, React hook useEffect runs continuously forever/infinite loop and the only comment is about useCallback() which I'm not familiar with.

    How I'm currently using useEffect() (which I only want to run once in the beginning similar to componentDidMount()):

    useEffect(() => {
        fetchBusinesses();
      }, []);
    
    const fetchBusinesses = () => {
        return fetch("theURL", {method: "GET"}
        )
          .then(res => normalizeResponseErrors(res))
          .then(res => {
            return res.json();
          })
          .then(rcvdBusinesses => {
            // some stuff
          })
          .catch(err => {
            // some error handling
          });
      };