React.memo prevents re-renders when props haven't changed via shallow comparison. But just wrapping a component in React.memo isn't enough — you need the right strategies to make it effective.
Strategy 1 — Basic memoization:
1const ExpensiveComponent = React.memo(({ data, onSelect }) => {2 // Expensive render logic here3 const processed = data.map(item => ({4 ...item,5 computed: heavyCalculation(item),6 }));78 return (9 <ul>10 {processed.map(item => (11 <li key={item.id} onClick={() => onSelect(item.id)}>12 {item.name} - {item.computed}13 </li>14 ))}15 </ul>16 );17});
Strategy 2 — Custom comparison function: When shallow comparison isn't enough (e.g., you want to ignore certain props):
1const UserCard = React.memo(2 ({ user, onClick, lastUpdated }) => (3 <div onClick={onClick}>4 <h3>{user.name}</h3>5 <p>{user.email}</p>6 </div>7 ),8 (prevProps, nextProps) => {9 // Only re-render if user.id or onClick changed10 // Ignore lastUpdated prop entirely11 return (12 prevProps.user.id === nextProps.user.id &&13 prevProps.onClick === nextProps.onClick14 );15 }16);
Strategy 3 — Combine with useCallback for functions:
1function Parent() {2 const [count, setCount] = useState(0);34 // BAD: new function every render = memo child always re-renders5 const handleClick = () => console.log("clicked");67 // GOOD: stable reference = memo child skips re-render8 const handleClick = useCallback(() => {9 console.log("clicked");10 }, []);1112 return (13 <div>14 <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>15 <MemoizedChild onClick={handleClick} />16 </div>17 );18}1920const MemoizedChild = React.memo(({ onClick }) => {21 console.log("Child rendered");22 return <button onClick={onClick}>Click me</button>;23});
Strategy 4 — Split contexts to limit re-render scope:
1// BAD: one context for everything — all consumers re-render on any change2const AppContext = createContext({ theme, user, settings, notifications });34// GOOD: split into separate contexts5const ThemeContext = createContext(theme);6const UserContext = createContext(user);7const SettingsContext = createContext(settings);89// Now each consumer only re-renders when its specific context changes
Strategy 5 — State colocation:
1// BAD: filter state at top = all children re-render on every keystroke2function App() {3 const [filter, setFilter] = useState("");4 return (5 <>6 <SearchInput value={filter} onChange={setFilter} />7 <ExpensiveList filter={filter} />8 </>9 );10}1112// GOOD: move state to where it's needed13function SearchableList() {14 const [filter, setFilter] = useState("");15 return (16 <>17 <SearchInput value={filter} onChange={setFilter} />18 <ExpensiveList filter={filter} />19 </>20 );21}
Decision framework — when is React.memo worth it?