What's interesting in React 18

Hello! I'm Dima, front-end developer at Surf. Today we’ll look at the most popular front-end library – React. What was in React18? Let's find out!

Reactdeveloped by Meta* (formerly Facebook*), remains one of the leading libraries for creating user interfaces.


The article will contain links to React documentation that lead to the official product website created by Meta*. The Meta* company is recognized as an extremist organization, its activities in Russia are prohibited.


With each new release, developers receive powerful tools and improvements that simplify development and improve application performance.

Looming on the horizon React 19which is already being tested and promises even more interesting features. It's time to remember what innovations he brought React 18 and how they influenced the work with interfaces.

1. Concurrent React: A New Era of Rendering

Concurrent React, introduced in React 18, is a fundamental update to the rendering model. It improves performance and smooth user experience.

The synchronous rendering we are accustomed to here runs continuously and blocks the interface until completion. In parallel, this is not always the case. React can stop rendering in the middle and continue later. Or stop it completely.

We are given a guarantee that the interface will look consistent even if rendering is interrupted. React defers changes to the DOM until the entire tree is ready – this avoids unwanted rendering.

The key advantage of parallel rendering is its ability to prepare multiple versions of the interface in parallel (what a twist!). So even if a large rendering task is running, it can quickly respond to user events: clicks, scrolls, etc.

One of the functions that uses the capabilities of Concurrent React is the component <Offscreen>. It will allow you to prepare interfaces in the background and save state. Which is especially useful when switching between screens.

In React 18 we saw the beginning of the introduction of parallel rendering. It already supports Suspensetransitions, and streaming server-side rendering—great for developing responsive, high-performance applications.

2. Automatic Batching

Automatic Batching is one of the key and pleasant innovations in React 18, which is aimed at improving productivity and ease of development.

Batching is a process where React combines multiple state updates into a single render to minimize the number of re-renders and make the application more responsive.

Before this process, batching only happened within React event handlers. If we updated the state inside other asynchronous operations (promises, setTimeout, or native event handlers), each update caused a separate rendering.

This led to poor performance – every state change forced React to render. And to avoid this, we had to wrap the update methods in batch. And this is often forgotten.

Status updates are now grouped automatically. Even if they happen in asynchronous operations or other contexts outside of standard React event handlers.

So if we call multiple state updates in an asynchronous function, React will only do one render after the changes are complete.

setTimeout(() => {
   setCount((c) => c + 1);
   setFlag((f) => !f);
}, 1000);

Previously, React would render twice, once for each state update. Now we are waiting for one render at the end. That's it, automatic batching.

Automatic batching also simplifies state management, especially in complex asynchronous scenarios. Now we don't have to manually control where and how packaging happens, making it easier to write clean, performant code.

3. Transitions

Transitions is a new concept in React 18. It allows you to distinguish between urgent and non-urgent UI updates.

Urgent updates — actions for which the user expects an immediate response: text input, button press, or mouse click. Such updates must be completed without delay.

Transitional (non-urgent) updates — changes that users do not expect to see immediately: changing filters or updating search results after selecting a criterion. These updates may be delayed and often require immediate visual feedback.

So, React 18 introduces two ways to work with transitions:

  1. startTransition

This function is used to indicate non-urgent updates within event handlers.

We can immediately update the state to display the entered text. And make updating the search results transitional so as not to block the interface:

import { startTransition } from 'react';

// Срочно: Отобразить введённое значение сразу
setInputValue(input);

// Переход: Обновить результаты поиска
startTransition(() => {
   setSearchQuery(input);
});

Here startTransition tells React that updating search results is transient and can be paused or canceled if the user takes another urgent action.

  1. useTransition

This is a hook that allows you to control transitions in functional components. It returns an array with two values:

  1. with boolean flag isPendingwhich indicates whether the transition is in progress;

  2. with a function to start the transition.

const [isPending, startTransition] = useTransition();

function handleInputChange(input) {
   setInputValue(input);

   startTransition(() => {
       setSearchQuery(input);
   });
}

This way we can track the state of the transition and, for example, show a loading indicator while the update is running in the background.

Interoperability with Parallel Rendering

Transitions are integrated with the new parallel rendering model. This allows React to interrupt non-urgent updates to ensure immediate response to urgent actions.

If the user is typing too quickly or the content takes longer to update, React can pause the transition. It will continue to show current content while a new update is being prepared in the background.

Transitions and parallel rendering work together to make React more flexible.

4. New Suspense functionality

React 18 added support for Suspense on the server and improved its capabilities with parallel rendering.

Suspense works best with the Transitions API.

It also allows you to declaratively control the loading state in the interface, especially when part of the component tree is not yet ready to be displayed.

How Suspense works

Components can pause rendering until they are ready to display. This allows us to replace the main content with alternative content. For example, replacing it with a loading indicator until the expected data or components become available:

<Suspense fallback={<Spinner />}>
   <Comments />
</Suspense>;

Here's the component <Comments /> not rendered – the data for its operation is not loaded. When they load, the user sees the component <Spinner />.

Server side support

In server rendering, we couldn't use Suspense until now. But now it can be used to work with slowly loading data when rendering on the server.

This allows client-side rendering to be deferred until sufficient data is available. This will help prevent loading lights from blinking or content from being partially displayed.

Integration with parallel rendering and transitions

When an update is triggered through a transition (for example, with startTransition), and the component is forced to wait for data to load, React will wait before updating the interface.

This means that the user can continue to see current data while new data is being prepared behind the scenes, without unnecessary loading indicators appearing.

import { Suspense, startTransition } from 'react';

function App() {
   const [page, setPage] = useState('home');

   const handleClick = () => {
       startTransition(() => {
           setPage('comments');
       });
   };

   return (
       <div>
           <button onClick={handleClick}>Show Comments</button>
           <Suspense fallback={<Spinner />}>
              {page === 'comments' && <Comments />}
           </Suspense>
       </div>
   );
}

5. New client and server rendering APIs

React DOM Client

New APIs for client rendering are available via react-dom/client and include:

  • createRoot – a new method for creating an application root that replaces ReactDOM.render. It is needed to use parallel rendering and automatic batching of updates;

  • hydrateRoot – a method for hydrating server rendering on the client and replaces ReactDOM.hydrate. It also supports the new features of React 18: parallel rendering and Suspense.

Both methods accept the option onRecoverableErrorwhich allows you to log errors during rendering or hydration. By default, React uses reportError or console.error in older browsers.

import { createRoot, hydrateRoot } from 'react-dom/client';

// Используем createRoot для новой инициализации приложения
const root = createRoot(document.getElementById('app'));
root.render(<App />);

// Используем hydrateRoot для гидратации серверного рендеринга на клиенте
hydrateRoot(document.getElementById('app'), <App />);

React DOM Server

New server rendering APIs available via react-dom/serverprovide full support for streaming and Suspense:

  • renderToPipeableStream. Used for streaming in Node.js environments. This method allows you to process large amounts of data and interact with the client while the server continues rendering;

  • renderToReadableStream. Designed for modern edge runtime environments (Deno and Cloudflare Workers) and supports data streaming taking into account the features of these platforms.

Old method renderToString still available. But you shouldn't use it for new projects – it doesn't support React 18 features (for example, Suspense).

import { renderToPipeableStream } from 'react-dom/server';
import express from 'express';
import App from './App';

// Инициализация Express сервера
const server = express();

server.get('/', (req, res) => {
   // Используем renderToPipeableStream для потокового рендеринга с поддержкой Suspense
   const stream = renderToPipeableStream(<App />, {
       onShellReady() {
           // Устанавливаем заголовок ответа
           res.setHeader('Content-Type', 'text/html');

           // Начинаем потоковую передачу ответа
           stream.pipe(res);
       },
       onShellError(error) {
           // Обрабатываем ошибки
           console.error(error);
           res.status(500).send('Internal Server Error');
       },
       onError(error) {
           // Логируем ошибки, но продолжаем рендеринг
           console.error(error);
       },
   });
});

server.listen(3000, () => {
   console.log('Server is listening on port 3000');
});
  • renderToPipeableStream. This method is used to stream the HTML response to the Node.js server. It is integrated with Suspense, which allows the server to send part of the content before the entire application is fully loaded;

  • Suspense in SSR. If the component is inside App uses Suspense to load data, the server can start sending a shell of HTML code to the client immediately after it is ready, without waiting for all data to be downloaded;

  • onShellReady. This callback is called when React is ready to start streaming HTML code. It is useful for sending the initial parts of a page before all content has been rendered;

  • onShellErrorAndonError. These callbacks allow you to handle and log errors that may occur during rendering and ensure that the response completes correctly.

6. New hooks

useId

useId — a hook for generating unique identifiers, which are the same on both the client and the server, which prevents inconsistencies during hydration.

This hook is useful in components that work with accessibility APIs that require unique identifiers such as aria-labelledby or aria-describedby.

Note that useId is not intended to generate list keys, which should be based on your data.

const id = useId();

return (
   <div>
       <label htmlFor={id}>Name</label>
       <input id={id} type="text" />
   </div>
);

useTransition

useTransition and related to it startTransition Allows you to classify status updates as non-urgent and leave the rest as urgent by default.

This allows React to pause non-urgent updates if urgent ones occur: text input, for example.

const [isPending, startTransition] = useTransition();

function handleChange(event) {
   startTransition(() => {
       setSearchQuery(event.target.value);
   });
}

useDeferredValue

useDeferredValue defers rendering of non-urgent parts of the user interface. This is similar to debouncing, but with advantages: there is no fixed time delay, deferred renders are interrupted in case of urgent updates.

const deferredValue = useDeferredValue(value);

return <List items={deferredValue} />;

useSyncExternalStore

useSyncExternalStore simplifies work with external state stores (stores) and ensures their synchronous updating. This is important for libraries that work with external data and state – the hook supports concurrent reading and eliminates the need for useEffect for subscriptions.

const state = useSyncExternalStore(
   subscribeToStore,
   getSnapshotFromStore
 );

Important! useSyncExternalStore It's best to use it in libraries rather than in custom application code.

useInsertionEffect

useInsertionEffect is designed for CSS-in-JS libraries and solves performance issues when injecting styles during rendering. This hook fires after the DOM has changed, but before layout effects start reading the new arrangement of elements. This is especially important in parallel rendering environments, where React can allow the browser to recalculate element placement.

useInsertionEffect(() => {
   // Вставляем стили в DOM
   const style = document.createElement('style');
   document.head.appendChild(style);
   return () => {
       document.head.removeChild(style);
   };
}, []);

Important! useInsertionEffect also intended for use in libraries, not application code.

These hooks open up new possibilities and optimizations, especially in the context of parallel rendering and working with external states.

What's the result?

React 18 is a really cool update. One of the most interesting innovations is parallel rendering, which really helps make interfaces faster and more responsive. Especially when it comes to complex applications with a lot of data.

We also really like the automatic batching feature, which eliminates a bunch of unnecessary rerenders – you no longer need to think about it yourself, React will solve this problem itself.

As developers, we're excited to see React continue to evolve, and we're really looking forward to what React 19 will bring us. It will be a step towards an even more convenient and productive tool for creating interfaces. And no, this is not an advertisement. We really love React!

More useful information about web development can be found in the Surf Web Team Telegram channel.

Cases, best practices, news and vacancies for the Web Surf team in one place. Join us!


*Meta Platforms Inc. (Facebook and Instagram) is recognized as an extremist organization, its activities in Russia are prohibited.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *