πŸ”§ Error Fixes

React useEffect Missing Dependency Warning β€” How to Fix It


React Hook useEffect has a missing dependency: 'fetchData'.
Either include it or remove the dependency array.

The ESLint exhaustive-deps rule is telling you that your useEffect uses a value that isn’t in its dependency array. If that value changes, the effect won’t re-run β€” which might be a bug.

When to Add the Dependency

If the effect should re-run when the value changes, add it:

// ❌ Warning: userId is missing
useEffect(() => {
  fetchUser(userId).then(setUser);
}, []);

// βœ… Effect re-runs when userId changes
useEffect(() => {
  fetchUser(userId).then(setUser);
}, [userId]);

When Functions Cause the Warning

// ❌ fetchData changes every render β†’ infinite loop if added
const fetchData = () => {
  fetch('/api').then(r => r.json()).then(setData);
};

useEffect(() => {
  fetchData();
}, [fetchData]); // πŸ’₯ new function every render

Fix with useCallback:

const fetchData = useCallback(() => {
  fetch('/api').then(r => r.json()).then(setData);
}, []);

useEffect(() => {
  fetchData();
}, [fetchData]); // βœ… stable reference

Or move the function inside useEffect:

useEffect(() => {
  const fetchData = () => {
    fetch('/api').then(r => r.json()).then(setData);
  };
  fetchData();
}, []); // βœ… no external dependency

When to Suppress the Warning

Sometimes you intentionally want the effect to run only once, even though it uses a value:

// Run once on mount, even though userId exists
useEffect(() => {
  analytics.track('page_view', { userId });
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

Only suppress when you’re sure the effect shouldn’t re-run. Add a comment explaining why.

Common Patterns

Fetch on mount:

useEffect(() => {
  fetch('/api/data').then(r => r.json()).then(setData);
}, []); // βœ… no dependencies needed

Fetch when a prop changes:

useEffect(() => {
  fetch(`/api/users/${id}`).then(r => r.json()).then(setUser);
}, [id]); // βœ… re-fetches when id changes

Event listener:

useEffect(() => {
  const handler = () => console.log('scrolled');
  window.addEventListener('scroll', handler);
  return () => window.removeEventListener('scroll', handler);
}, []); // βœ… set up once, clean up on unmount

The Rule of Thumb

  1. If the effect uses a value and should re-run when it changes β†’ add it
  2. If the effect uses a function β†’ move it inside or wrap with useCallback
  3. If you’re sure it should only run once β†’ suppress with a comment explaining why