How to fix React Hook useEffect has a missing dependency: in React

ReactINTERMEDIATEMEDIUMUpdated Dec 9, 2025ยท 97 views

The exhaustive-deps ESLint rule warns when a useEffect hook references variables or functions that are not included in its dependency array. This can cause stale closures where your effect uses outdated values from previous renders, leading to subtle bugs. Understanding and properly managing dependencies ensures your effects run with current values and behave predictably.

Quick fix

Fixes React Hook useEffect has a missing dependency:

// ESLint will warn about missing 'userId' dependency
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, []); // userId is missing here
  
  return <div>{user?.name}</div>;
}
  1. Identify the missing dependency in the ESLint warning
  2. Add the missing dependency to the dependency array
  3. Move functions or variables inside useEffect if only used there
  4. Wrap function dependencies in useCallback
react โ€” error
// ESLint will warn about missing 'userId' dependency
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, []); // userId is missing here
return <div>{user?.name}</div>;
}
React Hook useEffect has a missing dependency:

What this error means

The react-hooks/exhaustive-deps rule is part of the eslint-plugin-react-hooks package that helps prevent stale closure bugs in React hooks. When you use useEffect, React runs the effect function after rendering. If your effect references props, state, or other values from the component scope, it "closes over" those values. If those values change but you do not include them in the dependency array, your effect will continue using the old values from the previous render instead of the new ones. The ESLint rule detects this situation and warns you to either add the missing dependency to the array or refactor your code to remove the dependency. This rule is critical because stale closures are one of the most common sources of bugs in React applications using hooks.

Typical symptoms

  • ESLint warning in your editor or console: "React Hook useEffect has a missing dependency: 'variableName'. Either include it or remove the dependency array"
  • Effect using outdated prop or state values even after they change
  • Event handlers inside effects referencing old callback functions
  • API calls or side effects not re-running when they should
  • Component displaying stale data that does not update when dependencies change
  • Infinite loops when you add the dependency but it changes on every render
  • Tests failing intermittently due to timing issues with stale closures

Common causes

  1. Using props or state variables inside useEffect without listing them in the dependency array
  2. Referencing functions defined in the component body that are not wrapped in useCallback
  3. Accessing variables from parent scope that change over time but are omitted from dependencies
  4. Using object or array literals in the dependency array that get recreated on every render, causing infinite loops
  5. Relying on closure values that were captured during a previous render
  6. Conditionally including dependencies or incorrectly assuming certain values never change
  7. Using ESLint disable comments to suppress the warning without fixing the underlying issue

How to fix "React Hook useEffect has a missing dependency:"

1Identify the missing dependency in the ESLint warning

Read the ESLint error message carefully. It tells you exactly which variable is missing from the dependency array. For example: "React Hook useEffect has a missing dependency: 'userId'. Either include it or remove the dependency array." Locate the useEffect in your code and identify how the variable is being used inside the effect.

typescript
// ESLint will warn about missing 'userId' dependency
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, []); // userId is missing here
  
  return <div>{user?.name}</div>;
}
2Add the missing dependency to the dependency array

The most straightforward fix is to add the missing variable to the dependency array. This ensures the effect re-runs whenever that value changes, using the latest value each time.

typescript
// CORRECT - include userId in dependencies
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]); // Now re-runs when userId changes
  
  return <div>{user?.name}</div>;
}
3Move functions or variables inside useEffect if only used there

If a function or variable is only used inside the effect and does not need to be shared with other parts of the component, move it inside the useEffect. This removes the dependency requirement entirely.

typescript
// BEFORE - handleClick is a dependency
function MyComponent() {
  const handleClick = () => {
    console.log('clicked');
  };
  
  useEffect(() => {
    document.addEventListener('click', handleClick);
    return () => document.removeEventListener('click', handleClick);
  }, [handleClick]); // handleClick recreated every render
}

// AFTER - no external dependencies
function MyComponent() {
  useEffect(() => {
    const handleClick = () => {
      console.log('clicked');
    };
    
    document.addEventListener('click', handleClick);
    return () => document.removeEventListener('click', handleClick);
  }, []); // No dependencies needed
}
4Wrap function dependencies in useCallback

If a function is used in multiple places and cannot be moved inside useEffect, wrap it in useCallback with its own dependencies. This ensures the function identity remains stable unless its dependencies change.

typescript
function SearchComponent({ searchTerm, apiKey }) {
  // Memoize function so it only changes when dependencies change
  const performSearch = useCallback(() => {
    return fetch(`/api/search?q=${searchTerm}&key=${apiKey}`);
  }, [searchTerm, apiKey]);
  
  useEffect(() => {
    performSearch().then(data => console.log(data));
  }, [performSearch]); // Now safe to include
}
5Use the functional setState form to avoid state dependencies

When updating state based on previous state, use the functional form of setState. This removes the need to include the state variable in dependencies.

typescript
// WRONG - count must be in dependencies
function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(count + 1); // Uses stale count
    }, 1000);
    return () => clearInterval(timer);
  }, []); // Missing count dependency
}

// CORRECT - functional update does not need count in dependencies
function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(prev => prev + 1); // Always uses current count
    }, 1000);
    return () => clearInterval(timer);
  }, []); // No dependencies needed
}
6Use useRef for values that should not trigger re-renders

If you need to access a value inside an effect but do not want the effect to re-run when it changes, use useRef. The ref's current property can be updated without causing re-renders or requiring the ref itself in the dependency array.

typescript
function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);
  const latestCallback = useRef(null);
  
  // Update ref without triggering effect
  latestCallback.current = (msg) => {
    setMessages(prev => [...prev, msg]);
  };
  
  useEffect(() => {
    const socket = connectToRoom(roomId);
    socket.on('message', (msg) => {
      latestCallback.current?.(msg); // Always uses latest callback
    });
    return () => socket.disconnect();
  }, [roomId]); // Only roomId in dependencies
}
7Extract constant values outside the component

If a value never changes, move it outside the component entirely. This removes it from the dependency requirement and improves performance.

typescript
// BEFORE - DEBOUNCE_MS is inside component
function SearchBox() {
  const DEBOUNCE_MS = 300;
  
  useEffect(() => {
    const timer = setTimeout(() => performSearch(), DEBOUNCE_MS);
    return () => clearTimeout(timer);
  }, [DEBOUNCE_MS]); // Unnecessary dependency
}

// AFTER - constant moved outside
const DEBOUNCE_MS = 300;

function SearchBox() {
  useEffect(() => {
    const timer = setTimeout(() => performSearch(), DEBOUNCE_MS);
    return () => clearTimeout(timer);
  }, []); // DEBOUNCE_MS not needed
}
8Verify the fix by checking ESLint warnings

After making changes, verify that the ESLint warning is gone and your effect behaves correctly. Test that your component updates properly when dependencies change. Run your test suite to ensure no regressions were introduced. If you still see infinite loops or unexpected behavior, review your dependency array for objects or arrays that get recreated on every render.

Advanced notes

The exhaustive-deps rule is often disabled by developers who find it annoying, but doing so removes an important safety net. Stale closures are extremely difficult to debug because the symptoms are subtle and inconsistent. Instead of disabling the rule, invest time in understanding why it exists and how to properly satisfy it. If you encounter infinite loops after adding a dependency, the root cause is usually that the dependency (often an object or array) is being recreated on every render. Fix this by memoizing the value with useMemo or useCallback. For objects passed as props, consider whether you can pass primitive values instead or use React.memo with a custom comparison function. In cases where you genuinely need to suppress the warning (very rare), use the eslint-disable-next-line comment and document why it is safe. Never disable the rule globally. The React team maintains that satisfying the exhaustive-deps rule is always possible with proper refactoring. Tools like the React DevTools Profiler can help identify unnecessary re-renders caused by unstable dependencies. When working with external libraries that provide callbacks or event handlers, consider using refs to access the latest version without including them in dependencies.

Frequently asked questions

What does "React Hook useEffect has a missing dependency:" mean?
The react-hooks/exhaustive-deps rule is part of the eslint-plugin-react-hooks package that helps prevent stale closure bugs in React hooks. When you use useEffect, React runs the effect function after rendering. If your effect references props, state, or other values from the component scope, it "closes over" those values.
How do I fix "React Hook useEffect has a missing dependency:"?
The exhaustive-deps ESLint rule warns when a useEffect hook references variables or functions that are not included in its dependency array. This can cause stale closures where your effect uses outdated values from previous renders, leading to subtle bugs. Understanding and properly managing dependencies ensures your effects run with current values and behave predictably.
What causes "React Hook useEffect has a missing dependency:"?
Using props or state variables inside useEffect without listing them in the dependency array Referencing functions defined in the component body that are not wrapped in useCallback Accessing variables from parent scope that change over time but are omitted from dependencies
Is the "React Hook useEffect has a missing dependency:" error serious?
It is a medium-severity error โ€” more of a blocker than a risk. The steps above resolve it.

Official resources & further reading