Frontend Development 8 min read

When to Drop useCallback and useMemo? A Practical React Performance Guide

Swizec Teller argues that in most React projects the useCallback and useMemo hooks are unnecessary, explains how they work, shows common pitfalls, and provides concrete alternatives for writing cleaner, more efficient components without over‑optimizing.

Code Mala Tang
Code Mala Tang
Code Mala Tang
When to Drop useCallback and useMemo? A Practical React Performance Guide

Swizec Teller, a freelance engineer with over 20 years of experience, suggests simplifying React code by removing useCallback and useMemo because they are unnecessary in about 90% of cases.

"Honestly at this point useCallback is a code smell" — Swizec Teller

What useCallback Does

useCallback and useMemo are React hooks that create stable references for functions or objects, helping React’s rendering engine detect unchanged values.

<code>function FidgetSpinner() {
  const [spinning, setSpinning] = useState(false);

  const newFuncEveryTime = () => {
    setSpinning(!spinning);
  };

  const stableFunc = useCallback(() => {
    setSpinning(!spinning);
  }, [spinning]);

  return (
    <>
      <p>Is it spinning? {spinning}</p>
      <Spinner spinning={spinning} onClick={...} />
    </>
  );
}
</code>

Reference Stability and Re‑rendering

React decides whether to re‑render a component based on prop values. For functions and objects, the value is the memory address; a new reference triggers a re‑render even if the content is identical.

<code>const newFuncEveryTime = () => {
  setSpinning(!spinning);
};
</code>

Each render creates a new function, causing &lt;Spinner&gt; to re‑render.

useCallback creates a memoized function with a stable address, re‑instantiating only when its dependency array changes.

<code>const stableFunc = useCallback(() => {
  setSpinning(!spinning);
}, [spinning]);
</code>

This reduces unnecessary re‑renders.

Potential Pitfalls of useCallback

Using an incorrect dependency array can cause closure issues. For example:

<code>const stableFunc = useCallback(() => {
  setSpinning(!spinning);
}, []);
</code>

Here spinning is captured permanently, so the function no longer reflects the current state.

Why Developers Reach for useCallback

Common motivations include:

Performance concerns : developers fear unnecessary re‑renders and think memoizing callbacks will help, but this can add memory overhead.

Infinite‑loop issues : unstable callbacks used in useEffect dependencies cause the effect to run on every render, creating loops.

Sometimes developers misuse useCallback to create components, which is discouraged.

<code>function Component() {
  const SubComponent = useCallback(() => {
    return <div>This is a component damn it!</div>;
  }, []);

  return (
    <>
      <p>Lorem Ipsum</p>
      {SubComponent()}
    </>
  );
}
</code>

Avoiding useCallback

The best approach is to move functions outside the component scope and treat them as pure functions that depend only on their arguments.

Example 1 – Cleaner version

<code>function FidgetSpinner() {
  const [spinning, setSpinning] = useState(false);

  return (
    <>
      <p>Is it spinning? {spinning}</p>
      <Spinner spinning={spinning} setSpinning={setSpinning} />
    </>
  );
}

setSpinning(spinning => !spinning);
</code>

Example 2 – With react‑hook‑form

<code>function ComplicatedStuff() {
  const formMethods = useForm();
  const fieldValue = formMethods.watch("field");

  async function onSubmit() {
    await fetch('...', {
      method: 'POST',
      body: JSON.stringify({ fieldValue })
    });
  }

  return (
    <>
      <p>Live current value of field: {fieldValue}</p>
      <FormRenderComponent onSubmit={onSubmit} />
    </>
  );
}
</code>

In many cases you can simply let React handle the function without memoizing it.

When to Actually Use useCallback and useMemo

Use them when you are building a library or core functionality that will be shared across many components, when you have identified a genuine performance bottleneck, or when complex dependencies would otherwise cause frequent re‑renders.

Library or core code : frequent sharing across components may justify memoization.

Performance bottlenecks : only after profiling shows unnecessary re‑renders.

Complex dependencies : memoizing prevents spurious changes from triggering renders.

For expensive calculations, useMemo can dramatically improve performance by recomputing only when dependencies change.

<code>const expensiveCalculation = useMemo(() => {
  // complex calculation
  return result;
}, [dependencies]);
</code>

Both hooks can boost performance, but overusing them adds complexity and memory cost; use them wisely.

frontendperformancereactHooksuseCallbackuseMemo
Code Mala Tang
Written by

Code Mala Tang

Read source code together, write articles together, and enjoy spicy hot pot together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.