Learnitweb

Concurrent Rendering in React

What is Concurrent Rendering?

Concurrent Rendering is not a feature you “turn on” or call directly — it’s a set of internal rendering capabilities that React uses under the hood to make your application more responsive and efficient.

Introduced in React 18, it gives React the ability to start rendering, pause, interrupt, prioritize, and resume work as needed — similar to how an operating system manages tasks.

Why React Needed Concurrent Rendering?

Before React 18, React used synchronous rendering. This means once a state or prop change triggered a re-render, React would compute and apply the entire UI change in one go. That’s fast for small updates, but as applications grow in complexity, this can cause:

  • UI freezes when rendering large lists or components.
  • Delayed user interactions, such as typing input that feels laggy.
  • Janky transitions and animations.

React needed a better way to split rendering work into units, defer non-critical updates, and avoid blocking the main thread — especially on low-end devices or during intensive work.

How Concurrent Rendering Works?

Concurrent Rendering introduces interruptible rendering:

  • React can now pause rendering to let high-priority updates (like a key press) go through first.
  • React can cancel a rendering process if a newer one supersedes it (like fast typing).
  • Rendering can resume from where it left off.

This is achieved using cooperative scheduling powered by React Fiber, the underlying architecture that tracks the state of each component during rendering.

Enabling Concurrent Rendering

To use concurrent rendering, you must use the new root API introduced in React 18.

Switch from legacy to concurrent root:

// React 17 and below
ReactDOM.render(<App />, document.getElementById('root'));

// React 18 with concurrent rendering
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'));
root.render(<App />);

Once you’re using createRoot, React will enable concurrent features behind the scenes where possible.

Key Features Powered by Concurrent Rendering

1. startTransition() — Marking Updates as Non-Urgent

Some updates in your app don’t need to happen immediately. For example, updating a search filter while a user types doesn’t have to block the typing experience.

startTransition() tells React:

“This update can wait. Let more urgent things (like user typing) go first.”

Example: Search Input with startTransition

import { useState, startTransition } from 'react';

function SearchBox() {
  const [input, setInput] = useState('');
  const [results, setResults] = useState([]);

  function handleChange(e) {
    const value = e.target.value;
    setInput(value);

    startTransition(() => {
      const filtered = expensiveFilter(value); // Heavy computation
      setResults(filtered);
    });
  }

  return (
    <div>
      <input value={input} onChange={handleChange} />
      <ul>
        {results.map((r) => <li key={r.id}>{r.name}</li>)}
      </ul>
    </div>
  );
}
  • Typing remains responsive.
  • Filtering happens without blocking user interaction.

2. useTransition() — Tracking Pending Transitions

React also gives you a hook to manage transitions more elegantly: useTransition.

const [isPending, startTransition] = useTransition();
  • isPending: boolean that tells you if a transition is in progress.
  • startTransition: function to wrap your non-urgent update.

Example: Show Spinner During a Transition

function ProductList({ query }) {
  const [products, setProducts] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleSearch = (value) => {
    startTransition(() => {
      const result = searchProducts(value); // Heavy
      setProducts(result);
    });
  };

  return (
    <div>
      <input onChange={(e) => handleSearch(e.target.value)} />
      {isPending ? <Spinner /> : null}
      {products.map(p => <div key={p.id}>{p.name}</div>)}
    </div>
  );
}

Here, while React filters products in the background, the UI shows a spinner, giving users instant feedback.

3. useDeferredValue() — Deferring Expensive Renders

This hook delays updating a value until higher priority updates are done.

const deferredQuery = useDeferredValue(query);

Used when:

  • You want the input field to stay fast.
  • But you delay expensive rendering (like long list filtering).

4. <Suspense> — Pause UI Until Ready

<Suspense> lets you wait for a component or data to be ready before displaying it. With concurrent rendering, this becomes more powerful.

Example: Lazy Loading with Suspense

const LazyComponent = React.lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  );
}

React will pause rendering at this boundary and continue when the component is ready, without blocking the rest of the UI.

You can even nest Suspense boundaries to progressively reveal sections of the UI.