What‘s New in React 18 Alpha? Concurrency, Batching, the Transition API and More

React is by far the most popular JavaScript library for building user interfaces, powering hundreds of thousands of websites and applications. According to recent surveys, over 70% of front-end developers use React regularly, and its adoption continues to grow year over year.

A big part of React‘s success has been its commitment to delivering a fast, smooth user experience, especially as web applications become increasingly complex and feature-rich. In recent years, the React team has focused on improving performance through techniques like incremental reconciliation, virtualized lists, and code splitting.

React 18, now available in an alpha release, represents the next major evolution of the React runtime and developer experience. The key pillars of this release include:

  1. Concurrent rendering to unlock fluid, interactive UIs
  2. Automatic batching of updates for better efficiency
  3. Streaming server-side rendering for faster time-to-content

Let‘s dive deeper into each of these areas and why they matter for React developers and users.

A More Performant and Responsive UI with Concurrent React

One of the biggest challenges of building interactive applications is handling many different types of updates – UI interactions, data fetches, animations, etc. – while keeping the interface responsive to user input.

Historically, React has used a "push-based" model where updates are processed serially on a single thread. When a high-priority update (like a button click) interrupts a lower-priority one (like rendering a large list), the user can experience lag or even an unresponsive UI.

React 18 introduces a new "pull-based" model powered by a concurrent renderer. Rather than rendering updates in a blocking manner, React can pause, resume, and reorder work based on its relative priority. This means high-priority updates like user input can be handled immediately, even if a long-running render is in progress.

To better understand how this works, let‘s look at a simplified version of React‘s internal workings:

// Simplified React update loop
while (hasUpdates()) {
  const update = getNextUpdate();

  if (update.priority === ‘high‘) {
    // Interrupt any in-progress work and apply the update immediately 
    applyUpdate(update);
  } else {
    // Check if there are any pending high-priority updates
    if (hasHighPriorityUpdates()) {
      // Pause the current low-priority work 
      const suspendedWork = suspendCurrentWork(); 
      // Process the high-priority updates
      while (hasHighPriorityUpdates()) {
        applyUpdate(getNextUpdate());
      }
      // Resume the suspended work
      resumeWork(suspendedWork);
    } else {
      // No pending high-priority work, so continue with the update
      applyUpdate(update);  
    }
  }
}

In this pseudocode, React continuously checks for updates and applies them based on their priority. If a high-priority update comes in while low-priority work is being processed, React can suspend that work, apply the urgent update, and then resume the paused work.

This approach enables more fluid and responsive UIs, as demonstrated by this benchmark comparing current React to a prototype version with concurrent mode enabled:

React 18 concurrent mode benchmark
Credit: Benoit Girard from the React core team

As you can see, the concurrent React version is able to maintain a smooth frame rate (~60 FPS) even as additional work is scheduled, while the current (legacy) version drops to ~25 FPS as the main thread is blocked.

For developers, the new concurrent renderer is largely transparent – existing code should work without modification in most cases. However, there are some important changes to be aware of, particularly around the lifecycle of components and when side effects are executed. The React team has published a detailed migration guide to help with the transition to concurrent mode.

Smarter Updates with Automatic Batching

Another key aspect of React performance is minimizing unnecessary re-renders. Every time a component‘s state or props change, React has to do some work to figure out what parts of the UI need to be updated. Reducing the frequency of these updates can have a big impact on application speed.

One technique React has long used is called "batching" – grouping multiple state updates into a single re-render cycle. This is based on the observation that many state updates are triggered by the same user interaction or event. By batching these updates, React can avoid redundant work and improve performance.

Prior to React 18, batching only applied to updates triggered within React‘s own event handlers (e.g. onClick). Updates originating from other sources, like promises, timeouts, or native event handlers, would each trigger their own re-render:

function MyComponent() {
  const [count, setCount] = useState(0);
  const [items, setItems] = useState([]);

  async function handleClick() {
    // Triggers two separate re-renders in React 17 and earlier
    setCount(count + 1);
    setItems([...items, `Item ${count + 1}`]);

    await fetchSomething();

    // Also triggers a separate re-render
    setCount(count + 1);
  }

  return (
    // ...  
  );
}

React 18 extends automatic batching to all updates, regardless of where they originate. The previous example would only trigger a single re-render in React 18, even though the state updates happen in an asynchronous callback.

This change brings two main benefits:

  1. It makes React apps more efficient "out of the box", without developers needing to manually batch updates.
  2. It makes the behavior of state updates more consistent and predictable, since the timing of re-renders is no longer dependent on the source of the update.

Automatic batching has already shown promising results in real-world applications. For example, the Amazon home page team saw a ~10% reduction in render time after upgrading to React 18:

React 18 batching benchmark
Credit: Nate Hunzaker from the Amazon retail website team

As a developer, you generally won‘t need to think about batching – it will happen automatically in most cases. However, there are a few situations where you might want to opt out of automatic batching and force synchronous updates, such as when updating a ref or interacting with third-party libraries. For those use cases, React 18 provides an escape hatch API called flushSync.

Overall, automatic batching is a powerful addition to React that should improve performance for the vast majority of applications, without requiring any code changes. By reducing the number of unnecessary re-renders, React 18 apps will feel faster and more responsive to users.

Faster Page Loads with Streaming Server Rendering

Server-side rendering (SSR) is a popular technique for improving the perceived loading speed of React applications, especially on slower networks or devices. By generating HTML on the server and sending it to the client, SSR allows users to see and interact with your application before the JavaScript bundle has finished downloading and executing.

However, SSR in React has historically had some limitations. The traditional approach follows these steps:

  1. The server fetches all the data needed to render the application.
  2. The server renders the entire React application to HTML.
  3. The server sends the HTML to the client.
  4. The client downloads the JavaScript bundle.
  5. The client "hydrates" the server-generated HTML, attaching event handlers and making it interactive.

The key issue with this approach is that it‘s "all or nothing" – the client can‘t display anything until the entire HTML document is received, and can‘t interact with anything until the entire JavaScript bundle is downloaded and hydrated.

React 18 aims to improve SSR performance through a combination of streaming rendering and selective hydration, enabled by a new feature called React.lazy().

With streaming rendering, the server can progressively send HTML to the client as it‘s generated, rather than waiting for the entire document to be ready. The client can then display this partial HTML while the rest of the page is still being rendered on the server.

Here‘s a simplified example of how this works:

import { Suspense } from ‘react‘;

function MyPage() {
  return (
    <div>

      <Suspense fallback={<Spinner />}>
        <LazyComponent />
      </Suspense>  
    </div>  
  );
}

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

In this code, the LazyComponent is loaded and rendered asynchronously using React.lazy() and Suspense. On the server, React will initially render a fallback loading state (e.g. a spinner) while it waits for the lazy component to be imported and rendered.

As soon as the LazyComponent is ready, React will send the rendered HTML to the client as part of the existing document stream. The client can then display this HTML immediately, without waiting for the entire page to finish rendering on the server.

Selective hydration takes this a step further by allowing individual parts of the page to become interactive as soon as their required JavaScript code is loaded, rather than waiting for the entire application to hydrate.

Continuing the previous example, let‘s say LazyComponent depends on a third-party charting library that‘s relatively large. With selective hydration, React can make the rest of the page interactive while the charting library is still being downloaded, and then hydrate the LazyComponent only once its code is fully loaded.

The impact of streaming SSR and selective hydration on user-perceived performance is significant. By sending HTML to the client faster and hydrating components progressively, React 18 applications will feel much more responsive, even on slower connections.

From a developer perspective, these optimizations are largely transparent – you can use the existing React.lazy() and Suspense APIs to define loading states and asynchronous boundaries in your application. The main difference is that these boundaries now affect how your application is streamed and hydrated, in addition to chunking your JavaScript bundle.

Embracing the Future of React Development

React 18 represents a major milestone for the React ecosystem, bringing significant performance improvements and new capabilities for building fast, engaging user experiences.

The introduction of concurrent rendering and automatic batching will help React applications feel more responsive and efficient out of the box, without significant changes to existing code. And the SSR enhancements will make it easier to deliver instant loading experiences, even for large and complex applications.

At the same time, React 18 lays the foundation for some exciting future possibilities. The new concurrent renderer opens up opportunities for more advanced UI patterns and libraries, such as smooth layout transitions, interruptible rendering, and improved data fetching workflows.

As a developer who has worked with React for several years, I‘m thrilled to see the framework continuing to evolve and push the boundaries of what‘s possible with web application UIs. The React team has demonstrated a thoughtful approach to introducing new features, with a strong focus on incremental adoption and backward compatibility.

Of course, as with any major release, there will be some learning curve and potential challenges. Concurrent rendering in particular may require rethinking certain patterns and assumptions, such as how to handle side effects and when to use memoization for optimal performance.

But overall, I believe the benefits of React 18 far outweigh the costs. By providing a more efficient and streamlined foundation, React 18 will enable developers to build better experiences with less effort, and to push the boundaries of what‘s possible with web applications.

If you‘re a React developer, I encourage you to start experimenting with React 18 today. While it‘s still in alpha and not yet recommended for production use, getting familiar with the new features and paradigms will help you hit the ground running when the final release becomes available.

In the meantime, keep an eye out for additional resources and guidance from the React team and community. The React docs are being updated with detailed information on the new APIs and best practices, and there are already some great tutorials and examples showcasing what‘s possible with React 18.

As always, the React community is a fantastic place to learn, share ideas, and collaborate with other developers. Whether you‘re just getting started with React or you‘re a seasoned veteran, there‘s never been a better time to dive in and see what this incredible library can do.

So what are you waiting for? Go forth and build something amazing with React 18! I can‘t wait to see what the community creates with these powerful new tools and capabilities.

Similar Posts