Topic 13 / 14

Performance — memo, useMemo & useCallback

~10 min read  //  React Series  //  Coding India

First: Re-Renders Are Usually Fine

When state changes, the component and all its descendants re-render. That sounds wasteful but rendering is cheap — React diffs the output and touches only changed DOM. Optimise when you measure a problem, not preemptively: premature memoisation clutters code for zero felt benefit.

Measure with React DevTools → Profiler: record an interaction, see which components rendered and how long they took. Long bars or huge render counts = your targets.

React.memo — Skip Unchanged Children

Wrap a component and it skips re-rendering when its props are shallow-equal:

const CourseCard = React.memo(function CourseCard({ course, onSelect }) {
  return (
    <article onClick={() => onSelect(course.id)}>
      <h3>{course.title}</h3>
    </article>
  );
});

Classic use: a 500-row list where typing in an unrelated search box re-renders every row. Memoise the row, and only rows whose props changed re-render.

The Catch: Reference Equality

Memo compares props with ===. Objects and functions created during render are new every time — silently defeating memo:

// every render creates a new function → CourseCard re-renders anyway
<CourseCard course={course} onSelect={(id) => setSelected(id)} />

That’s what the other two hooks fix.

useCallback — Stable Functions

const handleSelect = useCallback((id) => {
  setSelected(id);
}, []);                       // same function object across renders

<CourseCard course={course} onSelect={handleSelect} />   // memo now works

useMemo — Cache Computations & Objects

// expensive derived data — recompute only when inputs change
const ranked = useMemo(
  () => [...courses].sort((a, b) => b.rating - a.rating),
  [courses]
);

// stable object identity for a memoised child or context value
const contextValue = useMemo(() => ({ user, login, logout }), [user]);

That second use matters for Context: an inline value={{ ... }} re-renders every consumer on every provider render.

Cheaper Wins First

  • Push state down — if only the search box uses query, hold the state in the search box, not the page. Smaller blast radius beats memoising the world.
  • Children as props escape re-renders<Layout>{children}</Layout>: when Layout’s own state changes, the children element (created by the parent) is unchanged and skipped.
  • Keys for list identity — wrong keys cause unnecessary unmount/remount churn.
  • Lazy-load routesconst Admin = React.lazy(() => import("./Admin")) with <Suspense> keeps the initial bundle small.

(The React Compiler, now stabilising, auto-memoises components — but it rewards exactly the patterns above: pure renders, immutable updates. Write idiomatic React and the tooling keeps making it faster.)