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.
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 <Spinner> 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.
Code Mala Tang
Read source code together, write articles together, and enjoy spicy hot pot together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.