How it works
The explanation, with examples you can run.
memo: skip a child
import { memo, useCallback, useState } from "react" type Item = { id: string; label: string } const Row = memo(function Row({ item, onSelect }: { item: Item; onSelect: (id: string) => void }) { return <li onClick={() => onSelect(item.id)}>{item.label}</li>}) export function List({ items }: { items: Item[] }) { const [query, setQuery] = useState("") const [selected, setSelected] = useState<string | null>(null) const handleSelect = useCallback((id: string) => setSelected(id), []) return ( <> <input value={query} onChange={(e) => setQuery(e.target.value)} /> <p>Selected: {selected}</p> <ul> {items.map((item) => ( <Row key={item.id} item={item} onSelect={handleSelect} /> ))} </ul> </> )}Typing in the input re-renders List. Each Row gets the same item object and the same handleSelect function as before, so memo skips it. Without useCallback, handleSelect would be a new function on every keystroke and every row would render again, memo or not. Note that memo only looks at props: a memoised component still renders when its own state changes or a context it reads changes.
useMemo: skip a calculation
import { useMemo } from "react" type Product = { name: string; price: number } export function Results({ products, query }: { products: Product[]; query: string }) { const matches = useMemo( () => products.filter((p) => p.name.toLowerCase().includes(query.toLowerCase())), [products, query], ) return <p>{matches.length} matches</p>}Worth it if products has tens of thousands of entries and the component renders often for unrelated reasons. For a hundred items, the filter takes microseconds and the memoisation adds code for nothing. Measure before you decide: wrap the calculation in console.time, or use the Profiler in React DevTools.
When memoisation helps
- A component that is slow to render, re-rendering often with the same props.
- A value or function passed to a memoised child, where a new identity would defeat its
memo. - A value used as an effect dependency, where a new identity would re-run the effect.
- A calculation that is measurably expensive.
Before reaching for it, check the structural fixes. Move state down into the component that uses it, so fewer components render on each change. Pass components as children: if a parent's state changes, elements it received as children were created by its own parent and don't render again.
The React Compiler
React Compiler, stable since version 1.0 in October 2025, is a build-time plugin that analyses components and inserts the equivalent of memo, useMemo and useCallback where they are safe. It relies on your code following the rules of React (pure render, hooks at the top level, no mutation of props and state); code that breaks them is skipped. In a project that uses it, write plain code and keep manual memoisation for the rare case the compiler can't handle. Either way, memoisation is an optimisation: React may discard cached values, so the program must behave correctly without them.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.