iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

React.memo

React.memo wraps a component so it skips re-rendering when its props are shallow-equal to the previous render. Pair it with useMemo (caches a computed value) and useCallback (caches a function reference) and you have the three knobs for hand-tuning re-renders. The trap: most components do not need them; profile first.

memo, useMemo, useCallback with a real bottleneck

EXAMPLE
import React, { memo, useMemo, useCallback, useState } from 'react';

// 1) A child that is expensive to render — pretend each row paints a chart
function ExpensiveRow({ item, onSelect }) {
  // Pretend-heavy work
  let acc = 0;
  for (let i = 0; i < 200_000; i++) acc += Math.sqrt(item.id + i);
  return (
    <li onClick={() => onSelect(item.id)}>
      {item.label} ({acc.toFixed(0)})
    </li>
  );
}

// 2) Wrap with memo — re-renders ONLY when props change by Object.is
const MemoRow = memo(ExpensiveRow);

// 3) Use useCallback so onSelect is the SAME reference across renders.
//    Without it, every render makes a new function -> memo's check fails -> all rows re-render.
export default function List({ items }) {
  const [selected, setSelected] = useState(null);
  const onSelect = useCallback((id) => setSelected(id), []);

  // 4) useMemo for derived data — recompute only when items change
  const sorted = useMemo(
    () => [...items].sort((a, b) => a.label.localeCompare(b.label)),
    [items],
  );

  return (
    <ul>
      {sorted.map((item) => (
        <MemoRow key={item.id} item={item} onSelect={onSelect} />
      ))}
    </ul>
  );
}

// 5) memo with a custom comparator for deep-ish checks
const DeepRow = memo(ExpensiveRow, (prev, next) => {
  return prev.item.id === next.item.id
      && prev.item.label === next.item.label;   // ignore other fields
});

// 6) When NOT to use memo
//   - Component is cheap to render. Hash + compare is not free either.
//   - Props are usually new every render (inline objects/arrays, anonymous funcs).
//     Either lift them to useMemo/useCallback or skip memo.
//   - You are reaching for memo to fix a perf bug — profile first. A million memos
//     do not fix a missing key, a single huge re-render, or N+1 child re-renders.

// 7) The React Profiler (DevTools) is the right starting point
//   - Open DevTools -> Profiler -> Record an interaction
//   - The flamechart shows which components render and why
//   - Look for unexpected wide rows under low-frequency updates

// 8) React Compiler (RC at the time of writing) automates much of this
//   If you can adopt the compiler, you write fewer useMemo/useCallback by hand —
//   the compiler does the same analysis and only memoizes what actually helps.

Why it matters

Reach for memo only after the React Profiler shows a measurable re-render cost. Default-memoising every component is a real net loss — each comparator runs on every parent render, the memory overhead is non-zero, and you trade obvious render-when-state-changes semantics for "why did this not update".

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
const Row = React.memo(function Row({ user }) { return <div>{user.name}</div>; });
Try it Yourself »

Discussion

Loading…