React.memo & Shallow Comparison
HardReact.memo is a higher-order component that memoizes a functional component, skipping re-renders when props have not changed. It uses shallow comparison by default and can accept a custom comparator. It is the primary tool for preventing unnecessary re-renders in child components.
Interactive Visualization
Key Points
- Wraps a component to skip re-render when props are shallowly equal
- Shallow comparison: checks top-level property references, not deep equality
- Must be paired with useCallback/useMemo for object and function props
- Custom comparator function can override the default shallow comparison
- Not useful if the component re-renders due to its own state or context changes
- Has overhead from the comparison, so only use for components that re-render often with the same props
Code Examples
Memoizing an Expensive Component
import { memo, useState, useCallback } from 'react' interface DataGridProps { rows: Array<{ id: string; values: number[] }> onRowClick: (id: string) => void } const DataGrid = memo(function DataGrid({ rows, onRowClick }: DataGridProps) { return ( <table> <tbody> {rows.map((row) => ( <tr key={row.id} onClick={() => onRowClick(row.id)}> {row.values.map((v, i) => <td key={i}>{v}</td>)} </tr> ))} </tbody> </table> ) }) function Dashboard() { const [selectedId, setSelectedId] = useState<string | null>(null) const [rows] = useState(() => generateRows()) const handleRowClick = useCallback((id: string) => { setSelectedId(id) }, []) return ( <div> <p>Selected: {selectedId}</p> {/* DataGrid skips re-render when selectedId changes */} <DataGrid rows={rows} onRowClick={handleRowClick} /> </div> ) } function generateRows() { return Array.from({ length: 100 }, (_, i) => ({ id: String(i), values: [i, i * 2, i * 3], })) }
React.memo plus useCallback ensures DataGrid only re-renders when rows or onRowClick actually change, not when selectedId changes
Custom Comparator
import { memo } from 'react' interface ChartProps { data: number[] width: number height: number label: string } const Chart = memo( function Chart({ data, width, height, label }: ChartProps) { return <canvas width={width} height={height} data-label={label} /> }, (prevProps, nextProps) => { // Only re-render if data or dimensions change, ignore label return ( prevProps.data === nextProps.data && prevProps.width === nextProps.width && prevProps.height === nextProps.height ) } )
A custom comparator lets you control exactly which prop changes should trigger a re-render, ignoring irrelevant props
Common Mistakes
- Wrapping every component in memo without profiling to see if it actually helps
- Passing new object or function references on every render which defeats memoization
- Using memo on components that almost always receive different props
Interview Tips
- Explain that memo is an optimization and should be guided by profiling
- Know the full chain: memo + useCallback + useMemo work together
- Discuss when the React Compiler will make manual memoization unnecessary