Performance Basics
Chapter 2 warned that every consumer of a context re-renders whenever its value changes — and left that as a tradeoff to accept for rarely-changing state. This chapter gives the actual tools for reducing unnecessary re-rendering generally, closing with the fix for that exact warning.
React.memo — Skipping a Component's Re-render
Without memo, clicking the button re-renders App — and by default, every child re-renders right along with its parent, regardless of whether that child's own props actually changed. memo(ExpensiveList) wraps the component so React first compares its new props against the previous render's props; if they're the same, React skips re-rendering it entirely, reusing the previous output instead.
items array above is recreated as a brand-new array on every single render of App — even though its contents look identical, it's a different array in memory each time, and memo's comparison is by reference, not by deep content. As written, clicking the button would still cause ExpensiveList to re-render, because items "changed" every time, defeating the whole point. This is exactly what useMemo exists to fix.
useMemo — Memoizing a Value
useMemo(calculateValue, dependencies) only re-runs calculateValue when something in dependencies has actually changed since the last render — with an empty array, the array of fruit strings is computed exactly once and the same array reference is reused on every subsequent render. Now ExpensiveList's memo comparison sees the identical items reference each time count changes, and correctly skips re-rendering.
useCallback — Memoizing a Function
A regular function defined inside a component (onSelect={(item) => ...} written inline, or even a named function declared in the component body) is also a new value every render — functions are compared by reference too, the same as arrays and objects. useCallback(fn, dependencies) is useMemo's counterpart specifically for functions: it returns the exact same function reference across renders as long as the dependencies haven't changed, which is what lets a memoized child receiving that function as a prop actually skip re-rendering.
Fixing the Context Re-render Warning from Chapter 2
Chapter 2's { theme, toggleTheme } object literal, written directly inside value={{ ... }}, was a brand-new object every time ThemeProvider rendered for any reason — even one completely unrelated to theme — forcing every consumer to re-render along with it. Wrapping it in useMemo means value only becomes a genuinely new object when theme itself actually changes, exactly addressing the warning raised back then.
useMemo and useCallback have their own (small) cost, and wrapping every single value and function in them "just in case" adds real complexity without necessarily improving anything — most components re-render fast enough that it's genuinely not worth thinking about. These tools earn their place specifically around expensive computations, large lists, and components wrapped in memo that need a stable prop identity to actually benefit. When performance hasn't been measured as an actual problem, plain useState/regular functions are usually the better default.
| Tool | Memoizes... | Use it for... |
|---|---|---|
| React.memo(Component) | A component's render output | Skipping a child's re-render when its props are unchanged |
| useMemo(fn, deps) | A computed value | Expensive calculations, or stable object/array identity for memo'd children |
| useCallback(fn, deps) | A function reference | Stable function identity for memo'd children's props |
Coding Challenges
Build a parent with an unrelated counter and a list component wrapped in React.memo, logging to the console every time the list renders. Use useMemo to keep the list's items array stable, and confirm in the console that incrementing the counter no longer re-renders the list.
📄 View solutionTake Challenge 1's setup and add an onSelect handler prop passed to the memoized list. First confirm (via the console log) that an inline arrow function still causes the list to re-render despite memo. Then fix it with useCallback.
📄 View solutionTake your ThemeContext/ThemeProvider from Chapter 2, add an unrelated piece of state to ThemeProvider (e.g. a render counter, logged whenever any consumer renders), and apply useMemo to the context's value object to confirm consumers stop re-rendering when that unrelated state changes.
📄 View solutionChapter 7 Quick Reference
- React.memo(Component) — skips re-rendering a component when its props are unchanged (by reference)
- useMemo(fn, deps) — recomputes a value only when its dependencies change; keeps object/array identity stable
- useCallback(fn, deps) — keeps a function's identity stable across renders
- Objects, arrays, and functions are compared by reference — a new one each render defeats
memoeven with identical contents - Wrap a context's
valuein useMemo to stop unrelated state changes from re-rendering every consumer - Measure before optimizing — these tools have real value, but aren't free, and most components don't need them
- That's React Intermediate complete — Advanced picks up with state management beyond Context next