How to Optimize React Concurrent Mode
React’s concurrent features allow applications to remain highly
responsive during intense rendering tasks by pausing, resuming, or
abandoning renders as user interactions occur. This article explores
practical strategies to optimize concurrent performance in React,
focusing on leveraging APIs like useTransition and
useDeferredValue, implementing smart component memoization,
and avoiding common bottlenecks that interrupt fluid rendering.
1. Prioritize State
Updates with useTransition
By default, all state updates in React are treated as urgent, which
can block the main thread during heavy renders. The
useTransition hook allows you to mark specific state
updates as non-urgent transitions.
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
// Urgent: immediately update input field
setInputValue(e.target.value);
// Non-urgent: defer rendering the heavy list
startTransition(() => {
setSearchQuery(e.target.value);
});
};When using useTransition, keep the transition callback
synchronous. The callback must contain only the state setter functions,
and any asynchronous code (like API calls) should be handled outside the
transition.
2. Defer
Expensive Rendering with useDeferredValue
When you do not control the state setter function directly (for
example, when receiving a prop from a parent component), use the
useDeferredValue hook. This hook returns a deferred version
of a value that lags behind the original value during heavy renders,
keeping the UI interactive.
const deferredValue = useDeferredValue(inputValue);By rendering the UI with deferredValue, React will first
render the screen with the old value, and then attempt to render the new
value in the background.
3. Strict Memoization with
React.memo
In Concurrent Mode, React may interrupt rendering a component tree to
handle an urgent user interaction, then resume rendering later. To
ensure React does not do unnecessary work when resuming or re-evaluating
components, you must memoize expensive child components using
React.memo.
- Combine with hooks: Always pair
React.memowithuseMemoanduseCallbackin the parent component to prevent object referential changes from breaking the memoization. - Keep props stable: If props change on every render,
React.memowill fail to prevent re-renders, wasting CPU cycles.
4. Keep Render Functions Pure and Fast
Because concurrent rendering allows React to start, pause, and discard render phases before committing them to the DOM, your component render functions may run multiple times for a single commit.
- No Side Effects: Never perform side effects (such
as mutations, fetch requests, or timers) inside the render body. Keep
them in
useEffect. - Minimize Computation: Move complex calculations out
of the render loop using
useMemo. If a computation is extremely heavy, offload it to a Web Worker or handle it asynchronously.
5. Optimize Suspense and Code Splitting
Concurrent features rely heavily on Suspense to handle
loading states. To optimize performance, wrap heavy components in
React.lazy and boundary them with Suspense.
This ensures that only the necessary code is loaded and rendered,
reducing the initial bundle size and shortening the transition time when
React switches between different UI states.