Understanding the ESLint Warning: useEffect and useCallback
Adam C. |

If you've ever seen this ESLint warning:

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

You're not alone — and you're probably asking:

  • Why is this happening?
  • Why does it behave differently in a component vs. a custom hook?
  • And do I really need to use useCallback() here?

Let’s break this down clearly.

Photo by W.S. Coda on Unsplash

✅ useEffect with an empty dependency array

This is a very common pattern:

useEffect(() => {
  updateSomething();
}, []); // Run once after mount

This works just fine in React — it's saying:

"Run this effect only once after the initial render."

But ESLint (specifically the react-hooks/exhaustive-deps rule) might still complain:

"You’re using updateSomething inside the effect, but it’s not in the dependency array."

🤔 Why the warning?

ESLint is trying to protect you from accidentally using stale or unstable references inside effects. It's checking for all variables and functions used inside useEffect, and if any are omitted from the dependency array, it throws a warning.

It doesn't care whether your effect is supposed to run once — it just checks that all references are declared.

🧩 Why does it behave differently in a component vs a custom hook?

✅ Inside a component:

function MyComponent() {
  const updateSomething = () => {
    // do something
  };

  useEffect(() => {
    updateSomething();
  }, []); // no ESLint warning in many cases

  return <div />;
}

ESLint can often see that:

  • The function is defined within the same component scope
  • It doesn't depend on props, state, or other reactive values
  • It's unlikely to change between renders

So it might not warn you here — but that’s a best-effort guess.

⚠️ Inside a custom hook:

export function useCustomHook() {
  const updateSomething = () => {
    // logic here
  };

  useEffect(() => {
    updateSomething();
  }, []); // ESLint WILL complain
}

In a hook, ESLint has no way of knowing if the function is truly stable.
It assumes the function might capture dynamic state or props.

So it warns you to either:

  • Include the function in the dependency array
  • Or wrap it in useCallback()

Note: The name useCustomHook is also critical. React and ESLint enforce hook rules only for functions starting with use.
If you named it customHookFunction, the hook rules wouldn't apply — but you'd break React's expectations for custom hooks.

✅ How to fix the warning

Option 1: Use useCallback

Wrap the function in useCallback:

const updateSomething = useCallback(() => {
  ...
}, []);

useEffect(() => {
  updateSomething();
}, [updateSomething]);

Now ESLint sees the function has a stable identity and a clear dependency list.

Option 2: Suppress the warning

If you're sure the function is stable and doesn't rely on changing variables, you can just suppress the warning:

// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
  updateSomething();
}, []);

This is perfectly safe when you know what you're doing — especially for things like DOM access or chart updates that only run once.

🧠 Why hook names and component names matter

React and ESLint apply different rules based on naming:

Function TypeNaming ConventionReact BehaviorESLint Behavior
ComponentCapitalized (e.g. MyComponent)Treated as componentLight hook enforcement
Custom HookStarts with use (e.g. useCustom)Treated as hookStrict hook rules applied
Regular FunctionAnything elseNot treated speciallyHook rules not applied

✅ Custom hooks must start with use for React and ESLint to recognize them and apply hook rules properly. Otherwise, calling useEffect or useState inside them can throw runtime or linting errors.

✅ TL;DR

SituationShould you use useCallback?Will ESLint complain?
Function used in useEffect✅ Yes, recommended✅ Probably
Defined inside a component🤷 Optional❌ Might not
Defined inside a custom hook✅ Yes✅ Will warn
Using function once on mount❌ Not required⚠️ Add comment if needed

🔚 Final thoughts

  • React is fine with you calling a function inside useEffect(() => {}, []).
  • ESLint just doesn't know whether it's safe — so it errs on the side of caution.
  • You can either use useCallback to be explicit, or suppress the warning if you’re confident it’s safe.

React gives you flexibility — ESLint just gives you suggestions. Use both wisely. ✅