How React caches computed values and function references to skip expensive recalculations.
Memoization is a caching technique where React stores the result of an expensive computation and returns the cached result on subsequent renders β skipping the computation entirely when the inputs haven't changed. useMemo caches computed values. useCallback caches function references. Both use a dependency array to decide when to recompute.
useMemo(() => expensiveComputation(a, b), [a, b]) calls the function on first render and stores the result. On re-renders, React checks if a or b changed. If not, it returns the cached result without calling the function again.
useCallback(fn, [deps]) is equivalent to useMemo(() => fn, [deps]). Instead of caching the return value, it caches the function object. This is important because JavaScript creates a new function object on every render β which breaks referential equality for child component props.
React compares each dep value using Object.is() (strict equality). Primitive values (numbers, strings, booleans) are compared by value. Objects and arrays are compared by reference β so a new object literal '{''}' in deps always triggers recomputation.
If all deps are equal to the previous render's deps, React returns the memoized value immediately without calling the factory function. The component still re-renders β but the expensive computation is skipped.
If any dep changed, React calls the factory function again, stores the new result, and returns it. The old cached value is discarded.
Caches the return value of a computation. Recomputes only when deps change. Useful for expensive calculations derived from props/state.
Caches a function reference. Equivalent to useMemo(() => fn, deps). Critical when passing callbacks to memoized child components.
Two objects/arrays are equal only if they point to the same memory address. '{''}' === '{''}' is false. useMemo/useCallback preserve identity across renders.
The second argument to useMemo/useCallback. React compares these values between renders to decide whether to recompute.
Higher-order component that memoizes a component's render output. Works best combined with useCallback to stabilize callback props.
1function ParentComponent({ items }) {2 const [filter, setFilter] = useState('');3 const [count, setCount] = useState(0);45 // useMemo: expensive filter only recomputes when items/filter change6 const filteredItems = useMemo(7 () => items.filter(item => item.name.includes(filter)),8 [items, filter]9 );1011 // useCallback: stable reference, doesn't cause Child re-render12 // when only 'count' changes13 const handleSelect = useCallback((id) => {14 console.log('Selected:', id);15 }, []); // no deps β function never needs to change1617 return (18 <>19 <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>20 <MemoizedChild items={filteredItems} onSelect={handleSelect} />21 </>22 );23}2425const MemoizedChild = React.memo(({ items, onSelect }) => {26 // Only re-renders when items or onSelect reference changes27 return items.map(i => <Item key={i.id} item={i} onSelect={onSelect} />);28});
Without memoization, every parent re-render creates new object/function references, causing React.memo'd children to re-render unnecessarily. useMemo and useCallback preserve referential stability, making React.memo effective and preventing cascading re-renders in large component trees.
Your analytics dashboard processes 10,000 rows of sales data to calculate aggregates (sum, average, percentiles) and format display values. A clock that updates every second causes the entire dashboard to re-render β and the 50ms data processing runs every second.
The data transformation runs inside the render function without memoization. Every re-render (even from the clock ticker) recomputes all 10,000 rows. The dashboard becomes sluggish because 50ms of computation blocks the main thread on every tick.
Wrap the data transformation in `useMemo(() => processData(salesData), [salesData])`. Now the heavy computation only re-runs when `salesData` actually changes. Clock ticks cause re-renders but skip the 50ms computation entirely β React reuses the cached result.
Takeaway: useMemo is designed for expensive computations, not every value. The general guideline: if a computation takes more than 1ms or processes more than 100 items, it's a candidate for useMemo. Profile before optimizing β don't guess.
You wrap a heavy `DataTable` component in React.memo, but React DevTools still shows it re-renders every time the parent's unrelated state changes. Memo appears to 'not work.'
The parent passes `onRowClick={(id) => handleClick(id)}` β an inline arrow function. Every parent render creates a NEW function reference. React.memo compares props by reference: `oldFn !== newFn`, so it always re-renders. Memo is working correctly β the props ARE different every time.
Wrap the handler in `useCallback`: `const onRowClick = useCallback((id) => handleClick(id), [handleClick])`. Now the function reference is stable across renders. React.memo's shallow comparison sees identical props and correctly skips re-rendering the DataTable.
Takeaway: React.memo and useCallback/useMemo are a PAIR β they must be used together. React.memo is useless if any prop is an unstable reference (inline function, inline object, inline array). Always stabilize props with useCallback/useMemo before wrapping a child in React.memo.