How does React.memo work, and what are its limitations with props containing objects or functions?
`React.memo` prevents re-renders by performing a shallow comparison of props, but it fails to detect changes in mutated objects or newly created inline functions/objects — leading to stale renders or missed optimizations.
Short Answer
React.memo wraps a component and skips re-rendering if its props pass a shallow equality check (Object.is) between renders — but it cannot detect deep changes in objects/arrays, nor does it treat equivalent inline functions or objects as equal across renders, often causing unexpected re-renders or missed optimizations.
Details
React.memo is a higher-order component that implements memoization at the component level. On each render, it compares the current and previous props using a shallow comparison: for primitives, it checks value equality; for objects/arrays/functions, it checks reference equality. If all props are Object.is-equal, React reuses the last rendered output (bailing out). Crucially, this means:
{ count: 5 } !== { count: 5 }(different references → re-render)() => {} !== () => {}(new function every render → re-render)- Mutating an object prop (e.g.,
obj.x = 42) preserves the reference →React.memosees “no change” but renders stale UI.
The default comparison can be overridden with a customarePropsEqualfunction — but doing so correctly requires careful deep comparison or selective key-based checks (and carries perf overhead).
Example
const UserProfile = React.memo(({ user, onEdit }: { user: User; onEdit: () => void }) => (
<div>
<h2>{user.name}</h2>
<button onClick={onEdit}>Edit</button>
</div>
));
// ❌ Problematic usage:
function Parent() {
const [count, setCount] = useState(0);
return (
<UserProfile
user={{ id: 1, name: 'Alex' }} // new object every render
onEdit={() => setCount(c => c + 1)} // new function every render
/>
);
}
// → UserProfile re-renders every time, despite logical equivalence.Bonus
To stand out: explain when not to use React.memo (e.g., cheap components, or when profiling shows no benefit), mention that hooks like useCallback and useMemo are often needed alongside React.memo to stabilize function/object props, and note that React.memo only affects parent-triggered re-renders — it won’t prevent re-renders caused by local state or context changes within the memoized component. Bonus points for referencing the official React docs’ warning: “Don’t rely on memo as a semantic guarantee — it’s a performance hint.”