React 18: Features Breakdown
Tags: javascript, webdev, react
Published: 2022-05-17 01:32:21

by [Arek Nawo](https://blog.openreplay.com/authors/arek-nawo)


React 18 has been [officially released](https://reactjs.org/blog/2022/03/29/react-v18.html), bringing many exciting, new features. In this release, React embraces concurrency with its improved rendering system and builds upon this foundation with performance-enhancing features like transitions or automatic batching. 

In this article, you’ll learn how these features work and what they mean for you as a React developer.

## Road to the official release

Before diving into all the features, let’s revisit the entire process behind the release of React 18, as it was pretty unique compared to prior versions. [React 17](https://reactjs.org/blog/2020/10/20/react-v17.html#no-new-features) didn’t bring many new features. It did, however, lay the groundwork for future updates by improving on the fundamentals and enabling seamless gradual adoption of new React features. The effect of those changes is only now visible with React 18.

The most significant change in the release process of React was a newly-created [React Working Group](https://github.com/reactwg/react-18) (WG) announced alongside [the official alpha release](https://reactjs.org/blog/2021/06/08/the-plan-for-react-18.html). The group’s goal is to gather feedback from the community and prepare the ecosystem for the upcoming changes. Additionally, it serves as a great source of knowledge about React’s inner workings.

Thanks to the improvements from React 17 and input from the Working Group, React 18 turned out to be a feature-packed release, with only a handful of breaking changes.

## Breaking changes

With new concurrent features being gradually adaptable and enabled on-demand, the breaking changes in React 18 were limited to simple API changes and some improvements to the stability and consistency of different behaviors across React.

### Client rendering APIs

One of the most noticeable changes is the [new root API](https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#updates-to-client-rendering-apis) with `createRoot()`. It’s meant to replace the existing `render()` function, providing better ergonomics and enabling new concurrent rendering features.

```javascript
import { createRoot } from "react-dom/client";
import App from "App";

const container = document.getElementById("app");
const root = createRoot(container);
root.render(<App />);
```

Notice that the new API is now exported from the `react-dom/client` module. 

Also changed are the unmounting and hydration APIs.

```javascript
// Unmount component at DOM node:
// ...
root.unmount();

// Hydration
import { hydrateRoot } from "react-dom/client";
// ...

const container = document.getElementById("app");
const root = hydrateRoot(container, <App tab="home" />);
```

Gone is the render callback due to issues in timing it correctly with Suspense. An alternative, although not a one-to-one replacement, is an effect inside the top component:

```javascript
import { createRoot } from "react-dom/client";
import { useEffect } from "react";
import App from "App";

const App = () => {
  useEffect(() => {
    console.log("render callback");
  });

  return <div></div>;
};
const container = document.getElementById("app");
const root = createRoot(container);
root.render(<App />);
```

### Automatic batching

The `createRoot()` API also serves as an entry to another improvement in React 18 - [automatic batching](https://reactjs.org/blog/2022/03/29/react-v18.html#new-feature-automatic-batching). In prior versions of React, state updates were already batched when done in React event listeners to optimize performance and limit re-renders. Beginning with React 18, the state updates will also be grouped in other places - like inside Promises, `setTimeout` callbacks, and native event handlers.

```javascript
const App = () => {
  const handleClick = () => {
    setA((a) => a + 1);
    setB((b) => b - 1);
    // Updates batched - single re-render
  };

  setTimeout(() => {
    setA((a) => a + 1);
    setB((b) => b - 1);
    // New (v18): Updates batched - single re-render
  }, 1000);

  // ...
};
```

This change, while usually desired and helpful, can be breaking. If your code depends on the component re-rendering in-between separate state updates, you’ll have to adapt it to the new batching mechanism or use the `flushSync()` function to force an immediate flush of changes.

```javascript
import { flushSync } from "react-dom";
// ...

const handleClick = () => {
  flushSync(() => {
    setA((a) => a + 1);
  });
  // Re-render
  flushSync(() => {
    setB((b) => b - 1);
  });
  // Re-render
};
```

### Strict mode updates

While React 18 is already full of new features, there’s still a lot coming. To get your code ready for that, `StrictMode` is [getting even more strict](https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#updates-to-strict-mode). Most importantly, `StrictMode` will test the component’s resiliency to a reusable state, simulating a series of mounts and unmounts. It’s meant to get your code ready for the upcoming feature (likely in the form of an `<Offscreen>` component), which will preserve the state across the component’s mounting cycles.

While it’ll certainly provide better performance in the future, for the time being, it’s one other thing you have to keep in mind when enabling `StrictMode`.

### Other changes

Apart from the mentioned changes, there are a few more that you might find breaking, depending on your React codebase.

Worth mentioning is that [React 18 will no longer support Internet Explorer](https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#dropping-support-for-internet-explorer), as React 18 now depends on modern browser features like `Promise` or `Object.assign`. Given that Microsoft will stop supporting the browser on [June 15 of this year](https://blogs.windows.com/windowsexperience/2021/05/19/the-future-of-internet-explorer-on-windows-10-is-in-microsoft-edge/), it’s only natural that React and other JS libraries will be dropping support as well. Those who still need to support IE will have to stay on React 17.

The rest of the changes are related to the stability and consistency of different React behaviors and are unlikely to affect your codebase. With that said, you can find the [complete list here](https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#other-breaking-changes).

## Concurrent React

[The concurrent renderer](https://reactjs.org/blog/2022/03/29/react-v18.html#what-is-concurrent-react) is a behind-the-scenes feature of React’s rendering system. It allows for concurrent rendering, i.e., preparing multiple versions of your UI, in the background, at the same time. This means better performance and smoother state transitions. 

While concurrency might seem like an implementation detail, it’s what powers most of the new features. In fact, concurrent rendering is only enabled when you use one of those features, like transitions, Suspense, or streaming SSR. That’s why it’s essential to know about and understand how concurrent rendering works.

### Transitions

One of the new features powered by concurrent rendering is [transitions](https://reactjs.org/blog/2022/03/29/react-v18.html#new-feature-transitions). Transitions are meant to be used with existing state management API to differentiate between urgent and non-urgent state updates. This way, React knows which updates to prioritize and which to prepare in the background with concurrent rendering.

To know when to use transitions, you’ll have to better understand how users interact with your app. For example, typing in a field or clicking a button are actions to which users expect an immediate response - a value to appear in a text field or some menu to be opened. However, searching, loading, or processing data (e.g., search bar, chart, filtering table, etc.) is something that the user expects will take some time. The latter is where you’d use transitions.

You can use the `useTransition()` hook to create a transition. The hook returns a function to start a transition and a pending indicator to inform you about the transition’s progress.

```javascript
import { useTransition, useState } from "react";

const App = () => {
  const [isPending, startTransition] = useTransition();
  const [value, setValue] = useState(0);

  function handleClick() {
    startTransition(() => {
      setValue((value) => value + 1);
    });
  }

  return (
    <div>
      {isPending && <Loader />}
      <button onClick={handleClick}>{value}</button>
    </div>
  );
};
```

Any state updates you commit inside the `startTransition()` callback will be marked as transitions, thus making other updates take priority. There’s also a separate `startTransition()` function if you can't use the hook, though it doesn’t inform you about the transition’s progress.

```javascript
import { startTransition } from "react";
// ...
startTransition(() => {
  // Transition updates
});
// ...
```

### Suspense updates

Transitions work best when combined with React Suspense. Thanks to [some improvements](https://reactjs.org/blog/2022/03/29/react-v18.html#new-suspense-features), Suspense now integrates well with concurrent rendering, works on the server, and might soon support use-cases outside `lazy()`-loaded components. When used with transitions, Suspense will avoid hiding existing content. Consider the following example:

```javascript
import { Suspense } from "react";
// ...

const App = () => {
  const [value, setValue] = useState("a");
  const handleClick = () => {
    setValue("b");
  };

  return (
    <>
      <Suspense fallback={<Loader />}>
        {value === "a" ? <A /> : <B />}
      </Suspense>
      <Button onClick={handleClick}>B</Button>
    </>
  );
};
```

Upon state change, `lazy()`-loaded component will trigger Suspense leading to the rendering of the `fallback` element. If you mark the state change as a transition, React will know that it should prepare the new view in the background while still keeping the current one visible.

```javascript
import { Suspense, useTransition } from "react";
// ...

const App = () => {
  const [value, setValue] = useState("a");
  const [isPending, startTransition] = useTransition();
  const handleClick = () => {
    startTransition(() => {
      setValue("b");
    });
  };

  return (
    <>
      <Suspense fallback={<Loader />}>
        <div style={{ opacity: isPending ? 0.7 : 1 }}>
          {value === "a" ? <A /> : <B />}
        </div>
      </Suspense>
      <Button onClick={handleClick}>B</Button>
    </>
  );
};
```

Now, even though the view won’t change when the transition is processed, you can still give user feedback by using the transition indicator to, for example, set container opacity. Combining the mentioned improvements with the future ability to use Suspense with asynchronous tasks other than `lazy()`-loaded components means Suspense will become one of the most powerful features of React.

### Server rendering improvements

Apart from Suspense support, there are a lot of other changes happening on the SSR side of React. Combining Suspense with SSR streaming and lazy hydration means your server-side rendered app will hydrate and be usable as soon as possible. Not only that, zero-bundle-size [server components are coming](https://reactjs.org/blog/2022/03/29/react-v18.html#server-components-is-still-in-development). They’re currently experimental but might become stable in later minor releases. With them, you’ll be able to reduce the JS code that’s served to the client, even further optimizing the performance and loading times of your React app.

## Gradual adoption

Thanks to mentioned changes from React 17, even with a large codebase, you should be able to adopt React 18 with ease, gradually. Not only can you use the new version in only selected parts of your app, but you can also opt-in to new features and behaviors gradually by migrating from `render()` to `createRoot()`. On top of that, even with `createRoot()`, you still get to adopt concurrent rendering step by step, as it’ll only be enabled when you use its feature. Overall, the migration process should be smooth and potentially even enjoyable.

## The future of React

While React 18 brought a lot of new features, you can already see new things on the horizon. Server components, Suspense for data fetching, and `<Offscreen>` component rendering are just some of the upcoming features.

React is evolving with its entire ecosystem, and I can’t wait to see what comes next!