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.

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.

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.