How to Fix ESLint useEffect Dependency Warnings in React
Problem
I was working on a React component when I got this ESLint warning:
React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array.My code looked like this:
function UserList({ userId }) { const [users, setUsers] = useState([]);
const fetchData = async () => { const response = await fetch(`/api/users/${userId}`); const data = await response.json(); setUsers(data); };
useEffect(() => { fetchData(); }, []); // ESLint warning here!
return <ul>{users.map(user => <li key={user.id}>{user.name}</li>)}</ul>;}I kept getting the warning and didn’t know what to do. I was tempted to just disable the rule with an eslint-disable comment.
Why This Warning Matters
The ESLint plugin eslint-plugin-react-hooks has a rule called react-hooks/exhaustive-deps that warns you when your useEffect dependencies are incomplete. This isn’t just a linting annoyance—it catches real bugs.
When you have a dependency array like [ ], you’re telling React: “This effect doesn’t depend on any props or state, so only run it once when the component mounts.” But if your effect uses variables like fetchData or userId, and those values change, your effect won’t re-run, leading to stale data or bugs.
From the React docs:
If you specify a list of dependencies, React will only re-run the effect if at least one of the dependencies in the list has changed.
Solution 1: Add Missing Dependencies
The simplest fix is to add the missing dependencies to the array:
function UserList({ userId }) { const [users, setUsers] = useState([]);
const fetchData = async () => { const response = await fetch(`/api/users/${userId}`); const data = await response.json(); setUsers(data); };
useEffect(() => { fetchData(); }, [fetchData]); // Added fetchData as dependency
return <ul>{users.map(user => <li key={user.id}>{user.name}</li>)}</ul>;}But wait—this causes an infinite loop! Every render creates a new fetchData function, so the effect runs again, causing a re-render, creating a new fetchData, and so on.
Solution 2: Use useCallback to Stabilize Functions
To fix the infinite loop, I wrapped fetchData with useCallback:
function UserList({ userId }) { const [users, setUsers] = useState([]);
const fetchData = useCallback(async () => { const response = await fetch(`/api/users/${userId}`); const data = await response.json(); setUsers(data); }, [userId]); // fetchData only changes when userId changes
useEffect(() => { fetchData(); }, [fetchData]); // Now this works correctly
return <ul>{users.map(user => <li key={user.id}>{user.name}</li>)}</ul>;}Now fetchData only gets recreated when userId changes. This is the pattern the ESLint rule is guiding you toward.
Solution 3: Move the Function Inside the Effect
Another clean approach is to move the function definition inside the effect:
function UserList({ userId }) { const [users, setUsers] = useState([]);
useEffect(() => { const fetchData = async () => { const response = await fetch(`/api/users/${userId}`); const data = await response.json(); setUsers(data); };
fetchData(); }, [userId]); // Only userId is the dependency now
return <ul>{users.map(user => <li key={user.id}>{user.name}</li>)}</ul>;}This approach avoids the need for useCallback and makes the dependencies clearer.
Solution 4: Use the Functional Update Form of setState
If your effect only calls setState and doesn’t need to read the current state, use the functional update form:
function Counter({ increment }) { const [count, setCount] = useState(0);
useEffect(() => { const interval = setInterval(() => { setCount(c => c + increment); // Functional update doesn't need 'count' in deps }, 1000);
return () => clearInterval(interval); }, [increment]); // Only 'increment' is needed
return <div>Count: {count}</div>;}Solution 5: Use useRef for Mutable Values
If you need a mutable value that doesn’t trigger re-renders and doesn’t need to be in the dependency array, use useRef:
function Timer() { const [seconds, setSeconds] = useState(0); const intervalRef = useRef(null);
useEffect(() => { intervalRef.current = setInterval(() => { setSeconds(s => s + 1); }, 1000);
return () => clearInterval(intervalRef.current); }, []); // Empty array is fine here
return <div>Seconds: {seconds}</div>;}Common Mistakes to Avoid
Never Suppress the Warning
useEffect(() => { fetchData(); // eslint-disable-next-line react-hooks/exhaustive-deps}, []);This hides the warning but not the bug. The ESLint rule is smarter than you think—it catches issues that will cause problems in production.
Don’t Lie About Dependencies
useEffect(() => { fetchData(); // Uses userId internally}, []); // Lying: saying it doesn't depend on anythingIf userId changes, your effect won’t re-run, leading to stale data.
Understand What Goes in Dependencies
The dependency array should include all values from the component scope that are used inside the effect:
- Props (like
userId) - State variables (like
users) - Functions defined in the component body (unless moved inside the effect)
The Key Insight
As one Reddit commenter put it: “Just add the ESLint rule around hooks and it will warn you.” The exhaustive-deps rule isn’t nagging you—it’s catching bugs. Every time I’ve suppressed this warning, I’ve regretted it later.
The warning is essentially asking: “Are you sure this effect should only run when these specific values change?” If you’re using something in the effect that’s not in the dependencies, you’re probably introducing a bug.
Summary
- Add all variables used in the effect to the dependency array
- Use
useCallbackfor functions to prevent unnecessary recreations - Move functions inside the effect if they’re only used there
- Use functional state updates when possible
- Never suppress the warning—fix the underlying issue instead
The ESLint exhaustive-deps rule is one of the most valuable tools in React development. Trust it, understand it, and your code will have fewer bugs.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 React Official Docs - ESLint Plugin for Hooks
- 👨💻 React Official Docs - useEffect
- 👨💻 Reddit Discussion on useEffect
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments