Suggest an editImprove this articleRefine the answer for “What is React.memo and why is it needed”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**React.memo** is a HOC that wraps a functional component and skips re-renders when props have not changed, using shallow comparison. ```jsx const Button = memo(({ label }) => <button>{label}</button>); // Custom comparison memo(Component, (prev, next) => prev.id === next.id); ``` **Key point:** pair with `useCallback` for function props, or memo's comparison fails every render.Shown above the full answer for quick recall.Answer (EN)Image**React.memo** is a higher-order component ([HOC](/questions/what-is-hoc)) that wraps a functional component and skips its re-render when props have not changed. ## Theory ### TL;DR - React re-renders every child when a parent re-renders, even if the child's props are identical. React.memo adds a props check before that happens. - Comparison is shallow: primitives compare by value, objects and functions compare by reference. - Wrap in memo when React Profiler shows a component re-rendering with unchanged props on every parent update. - Always pair with [`useCallback`](/questions/what-is-usecallback) or [`useMemo`](/questions/what-is-usememo) when passing functions or objects as props, or the comparison will fail every time. - The second argument lets you define custom comparison logic for complex cases. ### Quick example Without memo, `Button` re-renders on every parent state change. The inline `onClick` is a new function reference each time, so even wrapping in memo without `useCallback` does nothing: ```jsx import { memo, useState, useCallback } from 'react'; const Button = memo(({ onClick }) => { console.log('Button rendered'); return <button onClick={onClick}>Click</button>; }); function App() { const [count, setCount] = useState(0); // Same reference between renders const handleClick = useCallback(() => console.log('clicked'), []); return ( <> <Button onClick={handleClick} /> <button onClick={() => setCount(c => c + 1)}>+1</button> </> ); } ``` `Button` renders once. Clicking `+1` triggers `App` to re-render, but `handleClick` keeps its reference. Memo's comparison returns equal, and `Button` stays untouched. ### Key difference React's default reconciliation re-renders every child whenever a parent renders, regardless of whether the child's output would change. `React.memo` inserts a shallow `Object.is` check on each prop before the render call. If all props match, React reuses the cached render output and skips the function call and DOM diff entirely. In trees with many leaf nodes that rarely receive new data, this reduces CPU work on every parent update. ### When to use - List items that stay the same most of the time: wrap each `<ListItem />` in memo so unchanged items skip re-render when one item updates. - Components with slow render logic: charts, large filtered tables, nested lists. - Pure display components with no local state. - React Profiler shows a component consuming noticeable frame time with identical props across renders. Skip memo when: - The component is tiny and re-renders in under 0.1ms anyway. - Props change on every render (an inline function without `useCallback` always produces a new reference). - No profiler evidence of a problem. Memo adds comparison overhead even when it saves nothing. ### How React.memo works internally React stores the previous props and the last `ReactElement` output in the component's fiber node. On the next render, it runs `shallowEqual`: loops `Object.keys`, calls `Object.is` on each value pair. If every key matches, the cached element is returned and your component function never runs. The comparison costs roughly 1-2 microseconds. That only pays off if the saved render is meaningfully more expensive. In React 18 with concurrent mode, memo still applies during `startTransition` renders. Urgent updates bypass the queue and memo runs fresh on those. ### Custom comparison When shallow comparison is not enough, pass a second argument: ```jsx const UserCard = memo(({ user }) => { return <div>{user.name}</div>; }, (prevProps, nextProps) => { // true = skip re-render, false = allow re-render return prevProps.user.id === nextProps.user.id; }); ``` Return `true` means props are considered equal. Be careful with deep equality functions here: a slow comparison can cost more than the render it is trying to prevent. ### Common mistakes **1. Inline functions as props** ```jsx // Wrong: new function reference every render <MemoChild onClick={() => alert(1)} /> // Fix const handleAlert = useCallback(() => alert(1), []); <MemoChild onClick={handleAlert} /> ``` `Object.is(() => {}, () => {})` is always `false`. The comparison fails every time and memo does nothing. **2. Object literals created during render** ```jsx // Wrong: new reference on every render const user = { name: 'Alice', details: { age: 30 } }; return <UserProfile user={user} />; // Fix const user = useMemo(() => ({ name: 'Alice', details: { age: 30 } }), []); ``` Shallow comparison checks the object reference, not its contents. A new literal is a new reference. **3. Mutating props objects in place** If you pass `{ items: [] }` and mutate the array without replacing the reference, memo sees the same reference and skips the render. The UI shows stale data. Use immutable updates. **4. Memoizing tiny components without profiling first** A component that renders in 0.05ms costs more in comparison overhead than it saves. The React DevTools flamegraph shows exactly where time is being spent. Check there before adding memo anywhere. **5. Unstable children prop** ```jsx // Every render creates a new React element <MemoChild> <span>label</span> </MemoChild> ``` `children` is compared by reference like any other prop. A new JSX expression is a new object. Stabilize with `useMemo` or extract static children to a module-level constant. In practice, the most common issue is forgetting `useCallback` on event handlers, which quietly defeats memo on every component that receives them. ### Real-world usage - **React Window**: memoizes row components for lists with 10,000+ items. - **Material-UI**: `TableRow` is wrapped in memo to avoid re-renders during grid scroll. - **TanStack Table**: cell renderers are memoized to skip work during column sort and filter operations. - **TanStack Query**: generated hooks memoize selector output the same way `reselect` does for Redux. - **Next.js**: memo marks client-component boundaries inside server component trees to limit re-render scope. ### Follow-up questions **Q:** How does shallow comparison work when a prop is an object? **A:** React loops `Object.keys` and calls `Object.is` on each value. Primitives compare by value. Objects, arrays, and functions compare by reference. Two different object literals with identical contents are not equal to `Object.is`. **Q:** When does React.memo hurt performance? **A:** When the component renders faster than the comparison takes. A 0.05ms render versus 0.1-0.3ms for the props check saves nothing. Profile with React DevTools before adding memo. **Q:** What is the difference between React.memo and useMemo? **A:** `React.memo` wraps a component and caches its render output based on props equality. `useMemo` is a hook that caches any computed value inside a component. You often use both together: memo on the component boundary, `useMemo` to stabilize an object you pass down as a prop. **Q:** Can you control comparison manually? **A:** Yes. The second argument is `(prevProps, nextProps) => boolean`. Return `true` to skip, `false` to allow. Deep equality functions work here but add their own cost, so benchmark before using them. **Q:** How does React.memo behave with concurrent features in React 18? **A:** Memo works with `startTransition`. During low-priority transitions, React may interrupt and restart renders, and memo's comparison runs on each attempt. Urgent updates (direct user input) are not deferred, so memo runs fresh there too. The fiber node tracks the component type via the `MemoComponent` tag. ## Examples ### Basic: memoized status badge ```jsx import { memo, useState } from 'react'; const StatusBadge = memo(({ status }) => { console.log('StatusBadge rendered'); return <span className={`badge-${status}`}>{status}</span>; }); function Dashboard() { const [count, setCount] = useState(0); return ( <div> <StatusBadge status="active" /> <button onClick={() => setCount(c => c + 1)}>Clicks: {count}</button> </div> ); } ``` `StatusBadge` renders once. Every subsequent click updates `count` and re-renders `Dashboard`, but `status` is still `"active"`. String comparison passes, badge skips. ### Intermediate: memoized todo list item ```jsx import { memo, useState, useCallback } from 'react'; const TodoItem = memo(({ id, text, completed, onToggle }) => { console.log(`TodoItem ${id} rendered`); return ( <label> <input type="checkbox" checked={completed} onChange={() => onToggle(id)} /> {text} </label> ); }); function TodoList() { const [todos, setTodos] = useState([ { id: 1, text: 'Buy groceries', completed: false }, { id: 2, text: 'Write tests', completed: false }, ]); const handleToggle = useCallback((id) => { setTodos(prev => prev.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo ) ); }, []); return todos.map(todo => ( <TodoItem key={todo.id} {...todo} onToggle={handleToggle} /> )); } ``` Toggling item 1 creates a new object only for item 1. Item 2's props (`id`, `text`, `completed`, `onToggle`) are bit-for-bit the same, so its render is skipped. In a list of 200 items, one update skips 199 renders. ### Advanced: when memo fails on nested objects ```jsx import { memo, useState, useMemo } from 'react'; const UserProfile = memo(({ user }) => { console.log('UserProfile rendered'); return <div>{user.name} - {user.details.age}</div>; }); function App() { const [count, setCount] = useState(0); // Bug: new object on every render const user = { name: 'Alice', details: { age: 30 } }; return ( <> <UserProfile user={user} /> <button onClick={() => setCount(c => c + 1)}>+1</button> </> ); } ``` Every click creates a fresh `user` literal. Shallow comparison sees a new reference and re-renders every time. Memo does nothing. Fix: stabilize the object. ```jsx const user = useMemo(() => ({ name: 'Alice', details: { age: 30 } }), []); ``` Now `user` keeps its reference across renders unless the dependencies change, and memo works as expected.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.