React 18 is on the horizon, and it’s bringing some of the most significant changes to the library since hooks were introduced. As the alpha version has hit GitHub and the React team has shared their plans, there’s a lot to unpack and prepare for. Having spent the last few weeks digging through discussions and experimenting with the alpha, I’m excited to share what these changes mean for our codebases.

Understanding Concurrent React: It’s Not a Mode Anymore

One of the first things to understand about React 18 is an important shift in terminology. “Concurrent Mode,” which many of us have been hearing about for years, is being retired. Instead, we’re getting “Concurrent React” - and the distinction matters.

Concurrency is no longer something you opt into at the root of your application. Instead, it’s a behind-the-scenes mechanism that powers new features you can gradually adopt. This is fantastic news for incremental adoption, as it means we won’t need to rewrite our apps to take advantage of these improvements.

What Makes Concurrent Rendering Special?

So what exactly is concurrent rendering? In essence, it’s React’s ability to prepare multiple versions of your UI at the same time. The current React rendering model is synchronous and blocking - once it starts rendering, it doesn’t stop until it’s done, which can cause performance issues with complex UI updates.

With concurrent rendering, React can start rendering, pause, and continue later. It can even abandon a render altogether if a more urgent update comes in. This interruptible rendering gives React the ability to execute multiple tasks simultaneously, enabling it to:

  • Keep your app responsive during intensive rendering tasks
  • Show content as soon as it’s ready instead of waiting for everything
  • Avoid showing loading states that appear for only a brief moment (avoiding “layout thrashing”)

This capability underpins all the exciting features coming in React 18 and represents a fundamental improvement to React’s rendering approach.

The New Features Powered by Concurrency

Automatic Batching

Currently, React batches state updates inside event handlers but not in promises, timeouts, or native event handlers. In React 18, batching becomes more consistent:

// React 17: these setState calls trigger two separate renders
fetch('/api/data').then(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
});

// React 18: these will be automatically batched into one render
fetch('/api/data').then(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
});

This improvement alone could offer noticeable performance benefits for many applications with no code changes required.

The Transition API

Perhaps the most immediately useful concurrent feature is the new startTransition API. It allows you to mark UI updates as “transitions,” which React treats as lower priority.

Consider a search input that filters a large list. With traditional rendering, typing quickly would cause the UI to freeze as React struggles to update the filtered results for each keystroke:

// Without transitions, this can block the UI
function handleChange(e) {
  setSearchQuery(e.target.value);
  setFilteredResults(filterByQuery(e.target.value));
}

With transitions, we can prioritize the input’s responsiveness:

// With transitions, input remains responsive
function handleChange(e) {
  // Urgent: Show what the user typed
  setSearchQuery(e.target.value);
  
  // Mark filtering as a transition (lower priority)
  startTransition(() => {
    setFilteredResults(filterByQuery(e.target.value));
  });
}

For my clients with data-heavy dashboards, this is going to be a game-changer. The ability to keep inputs responsive while handling expensive updates in the background solves a pain point we’ve been working around with debouncing and other techniques for years.

Suspense: The Evolution from Legacy to React 18

Suspense has been around since React 16.6, but it’s undergoing significant improvements in React 18. To understand what’s changing, let’s look at how Suspense currently works versus its new implementation.

Legacy Suspense (React 16/17)

In the current version of React, Suspense was primarily designed for code-splitting with React.lazy(). When used for other purposes like data fetching, it had some quirky behavior:

  1. When a component inside a Suspense boundary suspended, React would put the DOM in an inconsistent state temporarily.
  2. React would hide this inconsistency with display: hidden and show the fallback.
  3. Most importantly, sibling components continued rendering even when a component suspended.

This approach was described by the React team as “a bit weird, but a good compromise for introducing Suspense in a backwards compatible way.”

Additionally, in React 17, a Suspense boundary with no fallback would be skipped entirely, which could lead to confusing debugging scenarios.

// Legacy Suspense behavior
function ProfilePage() {
  return (
    <div>
      <Suspense fallback={<Spinner />}>
        <ProfileHeader />  {/* If this suspends */}
      </Suspense>
      <Suspense fallback={<Spinner />}>
        <ProfileTimeline /> {/* This still renders! */}
      </Suspense>
    </div>
  );
}

Concurrent Suspense (React 18)

With React 18, Suspense becomes a first-class part of the rendering model:

  1. When a component suspends, React now interrupts siblings and prevents them from committing until data is ready.
  2. The mental model is similar to how try/catch works in JavaScript - the closest Suspense boundary above “catches” the suspending component, no matter how many components are in between.
  3. Suspense boundaries will be respected even if the fallback is null or undefined, making behavior more predictable.
// React 18 Suspense behavior
function ProfilePage() {
  return (
    <div>
      <Suspense fallback={<PageGlimmer />}>
        <ProfileHeader /> {/* If this suspends */}
        <Suspense fallback={<LeftColumnGlimmer />}>
          <Comments /> {/* These components don't render until ProfileHeader is ready */}
          <Photos />
        </Suspense>
      </Suspense>
    </div>
  );
}

In this example, if ProfileHeader suspends, the entire page will be replaced with PageGlimmer. However, if either Comments or Photos suspend, they together will be replaced with LeftColumnGlimmer. This lets you design Suspense boundaries that match your visual UI layout.

The new model is more intuitive and consistent with how developers expect components to behave. It also enables more sophisticated UIs that can reveal content progressively as it becomes available, rather than waiting for everything or showing too many spinners.

This revamped Suspense system works hand-in-hand with concurrent rendering to deliver a more responsive user experience, especially when dealing with data fetching and dynamic content.

Preparing Your Codebase for React 18

While React 18 is still in alpha, there are steps we can take to prepare our applications:

1. Identify Blocking Renders

Start identifying parts of your application where rendering blocks the main thread. User interactions that trigger heavy updates are prime candidates for transitions once React 18 is available. For now, consider using techniques like debouncing and virtualizing long lists.

2. Clean Up Effects

React 18’s concurrency model means components might mount and unmount multiple times without being visible to users (as React tries different UI states). Review your useEffect cleanup functions to ensure they properly cancel subscriptions, abort fetches, and clean up resources.

// Make sure your effects have proper cleanup
useEffect(() => {
  const controller = new AbortController();
  fetch('/api/data', { signal: controller.signal })
    .then(/* ... */);
  
  return () => {
    controller.abort(); // This is crucial in React 18
  };
}, []);

3. Fix Strict Mode Warnings

If you haven’t already, enable React’s Strict Mode. React 18 will build on Strict Mode to help identify components that aren’t prepared for concurrent rendering. Fix any existing warnings now to make the eventual migration smoother.

4. Follow the Working Group

The React 18 Working Group is hosting discussions on GitHub, and they’re publicly available to read. This is a goldmine of information about upcoming changes and best practices. Members of the working group are actively sharing feedback, asking questions, and contributing ideas that are shaping the final release. Following these discussions gives you early insight into how the APIs are evolving.

5. Consider Server Components

While not directly part of React 18, the React team is also working on Server Components, which will complement the concurrent rendering model. They allow components to run on the server without JavaScript overhead on the client. Start thinking about which parts of your app could benefit from this pattern.

Excitements

After experimenting with the alpha and examining real-world use cases, I’m convinced this is steps to the right direction.

One of my projects is a complex dashboard with multiple data visualizations that update based on filters. Currently, when users change filters, there’s a noticeable lag as all the visualizations update simultaneously. With transitions, we’ll be able to keep the UI responsive while updating these visualizations with a much smoother experience.

The React team is also clearly prioritizing incremental adoption. Unlike the jump to Hooks, which required significant mental model shifts, concurrent features can be adopted gradually, with each step providing tangible benefits.

Looking Ahead

React 18 represents a significant evolution, not just in performance but in how we think about rendering UI. The concurrent features provide new tools for creating responsive interfaces without sacrificing the declarative programming model we love about React.

While we don’t have a specific release date yet, the alpha is available for experimentation, and the APIs are stabilizing. For those of us building complex applications, it’s worth starting to prepare now, both mentally and in our codebases.

Want to learn more about React 18? Check out the official announcement post and join the discussions on GitHub to stay updated on the latest developments.