Performance Idioms
Targeted tools to cut wasted work - used after measuring, not as default decoration on every component.
Search across all documentation pages
Targeted tools to cut wasted work - used after measuring, not as default decoration on every component.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Wrap a component so React skips re-rendering when props are shallow-equal to the previous render.
const Row = memo(function Row({ item }: { item: Item }) {
return <li>{item.name}</li>;
});Cache a pure calculation that is actually expensive. Do not wrap trivial expressions.
const ranked = useMemo(
() => items.toSorted((a, b) => b.score - a.score),
[items],
);Keep function identity stable when passing callbacks to memoized children or dependency arrays that should not thrash.
const onSelect = useCallback((id: string) => {
setSelectedId(id);
}, []);Memoize row components and pass stable keys plus stable callbacks so only changed rows re-render.
{items.map((item) => (
<Row key={item.id} item={item} onSelect={onSelect} />
))}Inline style={{}} or options={{}} create new identities every render and defeat memo children.
const style = useMemo(() => ({ maxHeight: 320 }), []);
return <List style={style} />;Load heavy screens on demand so the initial bundle stays smaller.
const Editor = lazy(() => import("./Editor"));Wrap lazy components in Suspense to show fallback UI while the chunk loads.
<Suspense fallback={<Spinner />}>
<Editor />
</Suspense>Mark updates that can wait (filtering a big list) so urgent input stays responsive.
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
const q = e.target.value;
setInput(q);
startTransition(() => setFilter(q));
}Keep showing a deferred version of a value so the controlled input can update immediately while heavy UI lags behind.
const deferredQuery = useDeferredValue(query);
const results = useMemo(() => search(index, deferredQuery), [deferredQuery]);Remounting with a new key is often cheaper and clearer than manual reset effects for complex forms.
<CheckoutForm key={cartId} cartId={cartId} />Structure trees so a frequently updating parent does not own expensive static children - pass them as children from above.
function UpdatingClock({ children }: { children: React.ReactNode }) {
const [t, setT] = useState(() => Date.now());
// children identity from parent can skip re-render work inside
return (
<div>
<time>{t}</time>
{children}
</div>
);
}Do not mount ten thousand DOM nodes. Use a windowing library when scrollable lists get large.
// Render only visible rows via a virtualizer; map a window, not items.lengthCompute during render instead of useEffect that setStates derived values - fewer renders, fewer bugs.
const fullName = `${user.first} ${user.last}`;memo/useMemo have comparison and memory cost. Use React Profiler or why-did-you-render data before sprinkling them everywhere.
// Measure slow commits first, then memo the actual hot component.Prefer transitions and deferred values over blocking the main thread with huge synchronous renders in event handlers.
startTransition(() => {
setVisibleRange(computeRange(scrollTop));
});Stack versions: React 19 · TypeScript (strict) · concurrent features enabled by default
Reviewed by Chris St. John·Last updated Jul 19, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥