पाठ 20 / 25

memo, useMemo and useCallback

Skip work when inputs have not changed.

Stable references enable skipping

When a parent re-renders, its children re-render by default. memo(Component) skips re-rendering when props are shallowly equal. But a new function or object created on every render is a new reference, so it defeats memo. useCallback keeps a function stable and useMemo keeps a computed value stable between renders while dependencies are unchanged. Measure first with the React DevTools Profiler; the React Compiler can add much of this memoisation automatically where it is enabled.

Fewer wasted renders, fewer surprises

Memoisation, keys and error boundaries keep apps fast and resilient.

Three ideas: memoisation, keys and identity, error boundaries.
Figure 7.1 — Memoisation, keys and error boundaries.

Render counts with and without stable props, run

I ran this with React 19.3 and react-dom 19.3 on Node.js 22, compiling JSX with esbuild 0.28. Browser-style examples use jsdom 29 as a fake DOM (imported from a small setup-dom.mjs file) and act() to apply updates. After the initial render and three parent re-renders: a plain child renders 4 times, a memo child given a new inline function each time also renders 4 times, and a memo child given useCallback and useMemo values renders only once.

import "./setup-dom.mjs";   // test setup: jsdom provides a browser-like DOM in Node
import { useState, useCallback, useMemo, memo, act } from "react";
import { createRoot } from "react-dom/client";

const renders = { plain: 0, memoBadProp: 0, memoGood: 0 };
const Plain = ({ onPick }) => { renders.plain++; return <i />; };
const MemoBad = memo(({ onPick }) => { renders.memoBadProp++; return <i />; });
const MemoGood = memo(({ onPick, items }) => { renders.memoGood++; return <i />; });
function Parent() {
  const [n, setN] = useState(0);
  const inline = () => {};                               // new function every render
  const stable = useCallback(() => {}, []);              // same function across renders
  const items = useMemo(() => [1, 2, 3], []);            // same array across renders
  return (<>
    <button onClick={() => setN(n + 1)}>{n}</button>
    <Plain onPick={stable} /><MemoBad onPick={inline} /><MemoGood onPick={stable} items={items} />
  </>);
}
const root = createRoot(document.getElementById("root"));
await act(() => root.render(<Parent />));
for (let i = 0; i < 3; i++) await act(() => document.querySelector("button").click());
console.log(renders);

Output:

{ plain: 4, memoBadProp: 4, memoGood: 1 }

Profile before optimising

Most re-renders are cheap; add memoisation where the Profiler shows real cost.

त्वरित जाँच: Why does memo not help when you pass an inline arrow function as a prop?

  • memo ignores functions
  • A new function is created each render, so props are never equal
  • Arrow functions cannot be props
  • memo only works on classes
Answer

A new function is created each render, so props are never equal — Stabilise references with useCallback.