React 18: new hooks and how rendering has changed


In this article, we will look at some of the updates that React 18 has prepared for us – we will analyze automatic batching, concurrent rendering, changes in the rendering pause architecture on the server side, and new hooks.
You will also see the code examples to the updates, and what needs to be done to start using the new functionality.

Automatic batching

React 18 adds the ability to automatically batch update state for asynchronous operations. For example, promises, timeouts, and fetch requests. This has a positive effect on performance.
Batching in React refers to the process of grouping multiple state update calls into a single render step. In other words, batch processing.
Previously, grouping happened only inside event handlers.

function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);
  function handleClick() {
    // Before React 18 the following calls did not batch
    // Setting of the state occurs "after" the event in the callback of an asynchronous call
    fetchSomething().then(() => {
      setCount(c => c + 1); // Will provoke a rerender
      setFlag(f => !f); // Will provoke a rerender
    // In React 18
    fetchSomething().then(() => {
      setCount(c => c + 1); // Does not provoke a rerender
      setFlag(f => !f); // Does not provoke a rerender
// React will provoke a rerender only once, in the end 
  return (
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>

Concurrent rendering and new hooks

Concurrent rendering (competitive mode) is intended for smoother operation of the application on the user’s device. One of its scopes of use is interrupted rendering. Imagine that the user enters text into the search bar. The event updates the state of the component, and a new list of results is rendered. During this process, the input is stuck: the browser cannot update the text entered in the field because it’s rendering a new list of results. Competitive mode fixes this limitation and makes the rendering breakable.
Along with the new competitive rendering features, new APIs have also been added: state transitions, Suspense features, and new hooks.


An API method has been added to update the state of a component, which entails heavy calculations, for example, filtering a list. This allows for a significant improvement in user input and interface response as it marks heavy component updates as “transitions”.
In the API, it is represented as a startTransition function which includes state updates that are non-urgent.

import { startTransition } from 'react';
// Urgent update: displaying the entered text
// Marking state updates as transitions
startTransition(() => {
  // Transition: filtering the list by the entered keyword

startTransition is useful if you want to make user input fast with no UI freeze, and non-urgent operations were performed in the background.


In addition to startTransition, a new useTransition hook has appeared. It allows you to know the status of the transition:

import { useTransition } from 'react';
const [isPending, startTransition] = useTransition();


Returns a delayed version of the passed value that will “lag behind” the original value by a time equal to the timeout:

import { useDeferredValue } from 'react';
// ...
const [text, setText] = useState("text");
const deferredText = useDeferredValue(text, { timeoutMs: 2000 });

When the new hook is useful: you need to implement a responsive and fast user interface. The component the user is interacting with will quickly re-render on every input. In this case, unnecessary re-rendering of the heavy component will not occur.

Suspense upgrade

Suspense is designed to display a backup interface (spinner) while waiting for child components. Child components at this time can make asynchronous API calls, or be loaded via lazy load.
The main innovation is that the feature has become stable. It received major architectural changes under the hood and acquired the name of Concurrent Suspense. The name change will not affect React users in any way. The big change for users is to render child elements inside Suspense:

const App = () => {
  return (
<Suspense fallback={<Loading />}>
<SuspendedComponent />
<Sibling />

In React 17, the <Sibling /> component will be mounted and its effects will be called. Then it will be hidden.
Now in React 18, the <Sibling /> component will only be mounted after the <SuspendedComponent /> has loaded.


Used to define the order in which directly nested Suspense and SuspenseList components are loaded and displayed to the user.

<SuspenseList revealOrder="forwards">
<Suspense fallback={'Loading...'}>
<ProfilePicture id={1} />
<Suspense fallback={'Loading...'}>
<ProfilePicture id={2} />
<Suspense fallback={'Loading...'}>
<ProfilePicture id={3} />

There are times when the UI needs to display components in a specific order. If you wrap them in a SuspenseList, then React will not render the component until the previous one from the list is loaded.

Streaming SSR

Major improvements have been made to Suspense Server-Side-Rendering (SSR). Let’s look into the main features:

Selective hydration

The hydrate() method is used in SSR and allows you to hang event handlers on DOM-tree nodes that have been rendered on the server side. This allows for a better customer experience when the page is first loaded.
React will start hydrating the components based on user interaction with the site content. For example, when a comment is clicked, React prioritizes the HTML hydration of the parent blocks.
Another feature is that React will not block the UI during hydration – this process will occur when the browser is idle. Therefore, user events will be processed immediately.

HTML Streaming

It allows you to send HTML to the client without downloading all the data for rendering to the server. Once the data is received, it will be rendered on the server and sent to the client. For example, there is a block with comments, and we can asynchronously load information on them, giving HTML. When the comments are received, render them and send them to the client at the end of the HTML document.

What do you need to do to enjoy the new features of React 18?

Update. Installing the latest version

npm install react react-dom
yarn add react react-dom

Rendering API update

React 18 introduces a new root API that provides better ergonomics for managing roots. The new API also includes a new parallel rendering which allows you to use parallel functions.

// Before
import { render } from 'react-dom';
const container = document.getElementById('app');
render(, container);
// After
import { createRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = createRoot(container);

If your application uses server-side rendering with hydration, update hydrate to hydrateRoot:

// Before
import { hydrate } from 'react-dom';
const container = document.getElementById('app');
hydrate(, container);
// After
import { hydrateRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = hydrateRoot(container, );