React useMemo and useCallback
Optimize React performance by memoizing values and callbacks to prevent unnecessary re-renders.
TL;DR
- 01Use useMemo to cache expensive calculations between renders.
- 02Use useCallback to keep function references stable for optimization.
- 03Pass a dependency array to control when memoization resets.
Tips
- 01Use the React DevTools Profiler to measure actual performance gains before and after memoization, since it adds overhead.
Warnings
- 01Missing dependencies in the array causes stale closures and subtle bugs, so enable the ESLint rule to catch them automatically.
useMemo Basics
- Import
useMemofrom React at the top of your component file.import { useMemo } from "react"; - Wrap an expensive calculation to cache its result between renders.
const total = useMemo(() => { return items.reduce((sum, item) => sum + item.price, 0); }, [items]); - The hook returns the cached value, not the function itself.
- Use a dependency array to reset the cache when inputs change.
- Only use
useMemowhen the calculation is slow and frequent.
useCallback Basics
- Import
useCallbackfrom React at the top of your component file.import { useCallback } from "react"; - Memoize a callback function so its reference stays the same between renders.
const handleClick = useCallback(() => { console.log("clicked"); }, []); - Pass the memoized function to child components expecting stable references.
- Use a dependency array that includes every value the callback reads.
- The reference only changes when a dependency changes.
Dependency Arrays
- An empty dependency array
[]memoizes forever, never resetting.const value = useMemo(() => expensive(), []); - Include values that the calculation depends on in the array.
const filtered = useMemo(() => filter(items, search), [items, search]); - The cache resets when any dependency changes, then recalculates fresh.
- Forgetting a dependency can cause stale values and hard-to-find bugs.
- Use ESLint rules to catch missing dependencies automatically.
When to Use Them
- Use
useMemoonly for calculations that noticeably slow down rendering. - Use
useCallbackwhen passing functions to optimized child components. - Use
React.memo()alongsideuseCallbackfor component memoization. - Avoid premature optimization — measure first with DevTools profiler.
- Do not memoize simple values or callbacks — the overhead costs more.
Common Patterns
- Memoize object literals passed as props to prevent child re-renders.
const config = useMemo(() => ({ timeout: 5000, retries: 3 }), []); - Use
useCallbackwithuseEffectto avoid infinite loops.const fetch = useCallback(async () => { const res = await api.get(); setData(res); }, []); - Combine with
useMemoto memoize complex derived state. - Watch the React DevTools profiler to confirm memoization helps.
- Consider using a state management library for complex memoization needs.
FAQ
useMemo caches the return value of a function, while useCallback caches the function itself. Use useMemo for expensive computations and useCallback when you need a stable function reference to pass as a prop or dependency.
Only memoize when the computation is genuinely expensive (e.g., filtering large arrays, complex math) and runs on frequent re-renders. For simple transformations, the overhead of memoization outweighs any benefit.
This is a stale closure caused by missing dependencies in the dependency array. Any variable from the component scope that the function references must be listed in the array, or the callback will capture its initial value indefinitely.
No — it can actually hurt performance by adding memory and comparison overhead. useCallback is only beneficial when passing functions to memoized child components (React.memo) or when the function is listed as a dependency in another hook like useEffect.
List each prop used in the computation as a dependency: const result = useMemo(() => expensiveCalc(propA, propB), [propA, propB]). The memoized value recomputes only when those specific props change, not on every render.