Note useMemo caches the result of a computation. React may discard the cache under memory pressure, so never rely on it for correctness -- only for performance. Do not memoize everything by default; profile first.
Note useCallback(fn, deps) is identical to useMemo(() => fn, deps). Its primary use case is passing stable function references to memoized child components (wrapped with React.memo) to prevent their re-renders.
// All values from component scope used inside must be listeduseCallback(()=>fn(a, b),[a, b]);useMemo(()=>compute(x),[x]);
Example
functionChatRoom({ roomId, serverUrl }){// Recreated only when roomId or serverUrl changeconst connect =useCallback(()=>{const ws =newWebSocket(`${serverUrl}/rooms/${roomId}`);
ws.onopen=()=>console.log('Connected to', roomId);return ws;},[roomId, serverUrl]);useEffect(()=>{const ws =connect();return()=> ws.close();},[connect]);return<div>Chat:{roomId}</div>;}
Note Missing a dependency leads to stale closures. Including an unnecessary one causes pointless recalculations. Use the exhaustive-deps lint rule (eslint-plugin-react-hooks) to catch mistakes automatically.
constMemoizedComponent=React.memo(Component);// With custom comparisonconstMemoizedComponent=React.memo(Component,(prevProps, nextProps)=>{return prevProps.id=== nextProps.id;});
Example
import{ memo,useState,useCallback}from'react';constExpensiveRow=memo(functionExpensiveRow({ item, onToggle }){// This component only re-renders if item or onToggle changereturn(<tr onClick={()=>onToggle(item.id)}><td>{item.name}</td><td>{item.value.toFixed(2)}</td></tr>);});functionDataTable({ rows }){const[selected, setSelected]=useState(newSet());const handleToggle =useCallback((id)=>{setSelected(prev =>{const next =newSet(prev);
next.has(id)? next.delete(id): next.add(id);return next;});},[]);return(<table><tbody>{rows.map(row =>(<ExpensiveRow key={row.id} item={row} onToggle={handleToggle}/>))}</tbody></table>);}
Note React.memo does a shallow comparison of props. It only helps if the component re-renders with the same props frequently. Pair it with useCallback for function props and useMemo for object/array props.
// DO memoize when:// - Computation is genuinely expensive (sorting large arrays, complex math)// - Passing callbacks/objects to React.memo children// - Value is a dependency of another hook// DON'T memoize when:// - The computation is trivial// - The component rarely re-renders// - No downstream consumer cares about reference stability
Note Memoization has a cost: it uses memory to store results and adds comparison overhead. Only apply it where profiling shows a measurable benefit. Premature memoization adds complexity without improving performance.
const options =useMemo(()=>({ key: value }),[value]);useEffect(()=>{doSomething(options);},[options]);
Example
functionDataFetcher({ endpoint, filters }){// Without useMemo, a new object is created every render,// causing the effect to re-run endlesslyconst requestConfig =useMemo(()=>({
url: endpoint,
params: filters,
headers:{'Accept':'application/json'},}),[endpoint, filters]);useEffect(()=>{const controller =newAbortController();fetch(requestConfig.url,{...requestConfig,
signal: controller.signal,}).then(r => r.json()).then(setData);return()=> controller.abort();},[requestConfig]);}
Note When an object or array is a dependency of useEffect, memoize it to prevent infinite effect loops. Every render creates a new reference, and useEffect sees a 'change' even when the values inside are identical.
stable referencereferential equalityprevent infinite loopobject dependencymemoize for effect