Last updated: Apr 6, 2024
Reading timeยท5 min
The warning "React Hook useEffect has a missing dependency" occurs when the
useEffect
hook makes use of a variable or function that we haven't included in
its dependencies array.
To solve the error, disable the rule for a line or move the variable inside the useEffect hook.
Here is an example of how the warning is caused.
import React, {useEffect, useState} from 'react'; export default function App() { const [address, setAddress] = useState({country: '', city: ''}); // ๐๏ธ objects/arrays are different on re-renders const obj = {country: 'Chile', city: 'Santiago'}; useEffect(() => { setAddress(obj); console.log('useEffect called'); // โ๏ธ React Hook useEffect has a missing dependency: 'obj'. // Either include it or remove the dependency array. eslintreact-hooks/exhaustive-deps }, []); return ( <div> <h1>Country: {address.country}</h1> <h1>City: {address.city}</h1> </div> ); }
The issue is that we're making use of the obj
variable inside of the
useEffect hook, but we aren't
including it in the dependencies array.
One way to solve the error is to add the missing dependencies to the array.
import {useState, useEffect} from 'react'; export default function App() { const [count, setCount] = useState(0); const [age, setAge] = useState(0); useEffect(() => { setAge(count + 20); }, [count]); // ๐๏ธ included count return ( <div> <button onClick={() => setCount(count => count + 1)}> Increment count {count} </button> <p>Age: {age}</p> </div> ); }
The useEffect
hook makes use of the count
variable, so it has to be included
in the dependencies array.
However, when working with objects or arrays, we can't just add the dependency like we did when working with primitives (like strings or numbers).
It would cause an error because objects and arrays are compared by reference in JavaScript.
The obj
variable is an object with the same key-value pairs on each re-render,
but it points to a different location in memory every time, so it would fail the
equality check and cause an
infinite re-render loop.
One way to get around the warning is to disable the ESLint rule for a line.
import React, {useEffect, useState} from 'react'; export default function App() { const [address, setAddress] = useState({country: '', city: ''}); const obj = {country: 'Chile', city: 'Santiago'}; useEffect(() => { setAddress(obj); console.log('useEffect called'); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( <div> <h1>Country: {address.country}</h1> <h1>City: {address.city}</h1> </div> ); }
The comment above the dependencies array disables the
react-hooks/exhausting-deps
rule for a single line.
useEffect
hook is passed an empty array for the second parameter, it only gets called when the component is mounted and unmounted.useEffect
hookAn alternative solution is to move the variable or function declaration inside
of the useEffect
hook.
import React, {useEffect, useState} from 'react'; export default function App() { const [address, setAddress] = useState({country: '', city: ''}); useEffect(() => { // ๐๏ธ move object / array / function declaration // inside of the useEffect hook const obj = {country: 'Chile', city: 'Santiago'}; setAddress(obj); console.log('useEffect called'); }, []); return ( <div> <h1>Country: {address.country}</h1> <h1>City: {address.city}</h1> </div> ); }
We moved the variable declaration for the object inside of the useEffect
hook.
This removes the warning because the hook no longer has a dependency on the object because it is declared inside of it.
Another possible solution, which can be used rarely, but is good to know about, is to move the function or variable declaration out of your component.
import React, {useEffect, useState} from 'react'; // ๐๏ธ move function/variable declaration outside of the component const obj = {country: 'Chile', city: 'Santiago'}; export default function App() { const [address, setAddress] = useState({country: '', city: ''}); useEffect(() => { setAddress(obj); console.log('useEffect called'); }, []); return ( <div> <h1>Country: {address.country}</h1> <h1>City: {address.city}</h1> </div> ); }
This helps because the variable won't get recreated every time the App
component is re-rendered.
The variable will point to the same location in memory on all renders, therefore
useEffect
doesn't need to keep track of it in its dependencies array.
useMemo
hook to solve the errorAn alternative solution is to use the useMemo hook to get a memoized value.
import React, {useMemo, useEffect, useState} from 'react'; export default function App() { const [address, setAddress] = useState({country: '', city: ''}); // ๐๏ธ get memoized value const obj = useMemo(() => { return {country: 'Chile', city: 'Santiago'}; }, []); useEffect(() => { setAddress(obj); console.log('useEffect called'); // ๐๏ธ safely include in the dependencies array }, [obj]); return ( <div> <h1>Country: {address.country}</h1> <h1>City: {address.city}</h1> </div> ); }
We used the useMemo
hook to get a memoized value that doesn't change between
renders.
useMemo
hook takes a function that returns a value to be memoized and a dependencies array as parameters. The hook will only recompute the memoized value if one of the dependencies has changed.useCallback
hook to memoize a functionNote that if you're working with a function, you would use the useCallback hook to get a memoized callback that doesn't change between renders.
import React, {useMemo, useEffect, useState, useCallback} from 'react'; export default function App() { const [address, setAddress] = useState({country: '', city: ''}); // ๐๏ธ get memoized callback const sum = useCallback((a, b) => { return a + b; }, []); // ๐๏ธ get memoized value const obj = useMemo(() => { return {country: 'Chile', city: 'Santiago'}; }, []); useEffect(() => { setAddress(obj); console.log('useEffect called'); console.log(sum(100, 100)); // ๐๏ธ safely include in the dependencies array }, [obj, sum]); return ( <div> <h1>Country: {address.country}</h1> <h1>City: {address.city}</h1> </div> ); }
The useCallback
hook takes an inline callback function and a dependencies
array and returns a memoized version of the callback that only changes if one of
the dependencies has changed.
If none of the suggestions worked for your use case, you can always silence the warning with a comment.
import React, {useEffect, useState} from 'react'; export default function App() { const [address, setAddress] = useState({country: '', city: ''}); const obj = {country: 'Chile', city: 'Santiago'}; useEffect(() => { setAddress(obj); console.log('useEffect called'); // ๐๏ธ Disable the rule for a single line // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( <div> <h1>Country: {address.country}</h1> <h1>City: {address.city}</h1> </div> ); }
You can learn more about the related topics by checking out the following tutorials: