Skip to content

React warningReact: “React Hook useEffect has a missing dependency. Either include it or remove the dependency array” (react-hooks/exhaustive-deps)

The linter flags React Hook useEffect has a missing dependency: 'fetchData', and adding it either feels wrong or starts an infinite loop. The effect reads a prop, a state value or a function from the component that isn’t in its dependency array. React would keep running the effect with the old value, which is how stale data and skipped refreshes happen.

Short answer

Don’t silence the rule. If the missing item is a function, move the function inside the effect, so only the values it reads become dependencies. If it is an object or function you can’t move, wrap it in useCallback or useMemo. If the effect only updates state from its previous value, use the updater form setCount(c => c + 1) and drop count from the list. Then add whatever the linter still asks for.

What is missing, and how to add it without a loop

  1. 01

    A function from the component body

    “Every reactive value used by your Effect’s code must be declared in your dependency list.” React, Removing Effect Dependencies

    A function declared in the component is a new function on every render, so adding it makes the effect run every time. Move it inside the effect; then the linter asks only for the values it uses, such as userId.

    // warns: fetchUser is missing
    const fetchUser = () => api.get(`/users/${userId}`).then(setUser);
    useEffect(() => { fetchUser(); }, []);
    
    // works: the function lives in the effect
    useEffect(() => {
      api.get(`/users/${userId}`).then(setUser);
    }, [userId]);
  2. 02

    A function or object you can’t move

    “Validates that dependency arrays for React hooks contain all necessary dependencies.” React, exhaustive-deps

    If the function is also used outside the effect, or comes from a custom Hook, wrap it in useCallback (or an object in useMemo) with its own dependencies, so it only changes when they do.

    const fetchUser = useCallback(
      () => api.get(`/users/${userId}`).then(setUser),
      [userId]
    );
    useEffect(() => { fetchUser(); }, [fetchUser]);
  3. 03

    State that the effect also sets

    An effect that reads count to set count loops once count is a dependency. Use the updater form, which receives the latest value, and remove count from the list.

    useEffect(() => {
      const id = setInterval(() => setCount((c) => c + 1), 1000);
      return () => clearInterval(id);
    }, []);
  4. 04

    A function or object passed in as a prop

    An onChange or options prop created inline in the parent is new on every render, so listing it reruns the effect every time, often in a loop. Wrap it in useCallback or useMemo in the parent, or move the object literal out of the render. setState functions and refs are stable and never need listing. For logic that should read the latest props without rerunning the effect, React 19.2 adds useEffectEvent.

  5. 05

    An eslint-disable comment that hides a bug

    “Suppressing the linter leads to very unintuitive bugs that are hard to find and fix.” React, Removing Effect Dependencies

    Disabling the rule for the line keeps the warning quiet but leaves the effect reading old values. Remove the comment and use one of the fixes above.

Checked against the vendor’s docs on : React: Removing Effect Dependencies; React: exhaustive-deps lint rule; React reference: useEffect.

Rather hand it over?

Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.

Related: React “Maximum update depth exceeded” · React “Too many re-renders” · React useEffect with an async function

Questions

More on this error

I only want the effect to run once. Is [] wrong?

[] is right when the effect truly uses nothing from the component. If the linter flags it, the effect reads something that can change, and running it once means it keeps the first value forever. Move what it reads inside, or accept that it reruns when that value changes.

Why does my effect run twice in development?

Strict Mode runs each effect and its cleanup one extra time on mount in development, to show effects that need cleanup. It doesn’t happen in production, and it isn’t caused by the dependency array.

Can you fix it for me?

If one bug is the whole problem, the App Fix Ticket covers it: a written diagnosis first, then the fix. If the app has more going on, the Launch Check is a written check of the whole app. Each issue found is rated critical, high, medium or low, with where it is and how it was confirmed. No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.

A different error?

Describe it and get a written quote

Send the error and what you were doing. You get a written scope, a fixed price and a timeline; Simon replies by email, usually within one business day.