React Expert
Write fast React code and fix inefficient patterns. Profile first; optimize only where it matters.
Profile First
- Do not add
memo/useCallback/useMemopreemptively. - Use React DevTools Profiler to identify actual bottlenecks.
- React 19 compiler auto-optimizes; manual hooks still needed for third-party libs, effect deps, and expensive external-data computations.
Writing Fast Code (Prevention)
- Avoid object/array creation in render — Inline
{}or[]as props/context creates new references every render, breaking memo and causing child re-renders. - Stable callbacks — Use
useCallbackwhen passing handlers to memoized children or to effects; otherwise prefer inline functions for simplicity. - Expensive computations — Use
useMemoonly for costly derivations (filtering/sorting large lists, heavy transforms). - Context — Split contexts by update frequency; avoid putting frequently-changing values in a single context.
- Code splitting — Use
React.lazy+Suspensefor route-level or heavy below-the-fold components. - Lists — Virtualize long lists (react-window, @tanstack/react-virtual) when rendering 100+ items.
- Async React — Use
useTransition,useOptimistic, anduseActionStatefor non-blocking updates and optimistic UI.
Async React (useTransition, useOptimistic, useActionState)
- useTransition —
[isPending, startTransition]. Wrap state updates and async work instartTransitionto keep the UI responsive. Transitions are non-blocking and can be interrupted (e.g. user clicks another tab). UseisPendingfor loading feedback. Caveat: state updates afterawaitmust be wrapped in anotherstartTransition(current limitation). - useOptimistic —
[optimisticState, setOptimistic]. Show instant UI feedback while an Action runs; React reverts when the Action completes. CallsetOptimisticonly inside an Action (insidestartTransition); otherwise React warns and the optimistic state briefly flashes. Pairs withuseTransitionfor async actions. - useActionState —
[state, dispatchAction, isPending]. Manages state from async Actions (e.g. form submissions). The reducer action can be async and perform side effects. CalldispatchActiononly from an Action (startTransitionor action prop). Good for forms, mutations, and progressive enhancement with Server Functions.
Pattern: Use startTransition for the async wrapper, useOptimistic for instant feedback, and useActionState when you need action-derived state (e.g. form errors, mutation result).
Fixing Inefficient Code (Optimization)
- Re-render cascades — Trace from parent; fix by memoizing children, stabilizing props, or splitting context.
- Object/array in props — Extract to
useMemoor move outside component. - Inline functions as props — Wrap in
useCallbackwhen child is memoized and re-renders are costly. - Heavy work in render — Move to
useMemooruseEffect+ state. - Large lists — Add virtualization; avoid mapping over thousands of items.
Patterns to Avoid
memoon every component (adds overhead; use only where profiling shows benefit).- Creating components inside other components (new reference each render).
- Spreading
...propsinto memoized children when props include unstable values. - Single giant context for entire app state.
Examples
Inline object in props (before → after):
// Before: new object every render, breaks memo
<ExpensiveChild style={{ margin: 8 }} />
// After: stable reference
const style = useMemo(() => ({ margin: 8 }), []);
<ExpensiveChild style={style} />
Context split (before → after):
// Before: theme + user both in one context; theme changes re-render user consumers
const AppContext = createContext({ theme: 'light', user: null });
// After: split by update frequency
const ThemeContext = createContext('light');
const UserContext = createContext(null);
Async with useTransition + useOptimistic:
function LikeButton({ isLiked, onLike }) {
const [isPending, startTransition] = useTransition();
const [optimisticLiked, setOptimisticLiked] = useOptimistic(isLiked);
function handleClick() {
startTransition(async () => {
setOptimisticLiked(!optimisticLiked);
await onLike();
});
}
return (
<button disabled={isPending}>
{optimisticLiked ? 'Liked' : 'Like'}
</button>
);
}
Checklist
- Profile before optimizing
- No inline objects/arrays as props to memoized children
- Callbacks passed to memoized children wrapped in
useCallbackwhen needed - Expensive derivations in
useMemo - Long lists virtualized
- Heavy/route-level components lazy-loaded
- Context split by update frequency
- Async actions use
useTransition; optimistic UI usesuseOptimistic; form/action state usesuseActionState