Programinja
Published in

Programinja

React 18 Release Candidate is Released | Learn React 18, it's APIs and SSR Improvements

Programinja | thealiraza | React 18 Release Candidate is finally here.
React 18 Release Candidate is finally here.

Finally, the wait is over!

React, one of the most widely used JavaScript libraries, has finally rolled out the release candidate of its highly waited version 18 or React 18. It means, only a few changes will be expected at all in the final/stable release.

The knowledge of React is required to understand the React 18 updates. If you are a newbie, just follow this article for a roadmap in React.

Let’s see what it is bringing!

The New Root API

React 18 has two root APIs, the older root API and the new one.

What is Root API?

As per React official docs, a “root” is a pointer to React’s top-level data structure to track a tree to render.

Legacy Root API

The existing Root API is called ReactDOM.render. Currently, the root is attached to the DOM element and can be accessed through the DOM node.

It will work precisely as in React 17. However, the console will throw a warning indicating its deprecation and moving to the latest API.

import  React from 'react';
import ReactDOM from 'react-dom';
import './index.scss';
import App from './App
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);

New Root API

It comes with ReactDOM.createRoot, enables the new concurrent renderer and is a gateway to all the improvements introduced with React 18.

import * as ReactDOMClient from 'react-dom/client';
import App from 'App';

const container = document.getElementById('app');

// Create a root.
const root = ReactDOMClient.createRoot(container);

// Initial render: Render an element to the root.
root.render(<App tab="home" />);

// During an update, there's no need to pass the container again.
root.render(<App tab="profile" />);

The createRoot function will create the root first and then the render method to be called on that.

How to handle hydration with the new Root API?

Hydration is now handled via hydrateRoot API.

import * as ReactDOMClient from 'react-dom/client';
import App from 'App';

const container = document.getElementById('app');
//Before
// Render with hydration.
ReactDOM.hydrate(<App tab="home" />, container);
//After
// Create *and* render a root with hydration.
const root = ReactDOMClient.hydrateRoot(container, <App button="add" />);

// You can later update it.
root.render(<App button="update" />);

Render Callback and new Root API

Render callback is removed because it can’t accommodate the improvements provided via partial hydration and SSR streaming.

import * as ReactDOMClient from 'react-dom/client';

const rootElement = document.getElementById("root");
// Don't do this
ReactDOMClient.render(<App />, rootElement, () => console.log("renderered"));
// Do this
const root = ReactDOMClient.createRoot(rootElement);
root.render(<App callback={() => console.log("renderered")} />);

Instead, React recommends the usage of requestIdleCallback, setTimeout, or a ref callback on the root.

A summary of differences between legacy and new root API

  • Render callback has been removed, and the hydration function working is changed.
  • A root is an object, and any function to update the root is applied on this object rather than on DOM.

Automatic Batching

Have you ever updated multiple states in a single function called by an event handler? You might have noticed that all states get updated in a single render. It is called automated batching.

Batching is when React groups multiple state updates into a single re-render for better performance.

Before React 18, batching was only done in React event handlers by default.

Moving ahead with the new Root API, batching can be done irrespective of the origin, such as promises, setTimeout, native event handlers. The update will improve overall performance and rendering time.

Disable Automated Batching

If the use case requires updating the state and waiting for a response before performing another action, ReactDOM. flushSync() comes to rescue.

import { flushSync } from 'react-dom';

function handleClick() {
flushSync(() => {
// setState function
});
// React has updated the DOM by now
flushSync(() => {
// setState function
});
// React has updated the DOM by now
}

Suspense/Streaming Server-side Rendering (SSR) and Selective Hydration

In layman’s language, server-side rendering refers to generating HTML content on the server and serving to the client until JavaScript is wholly loaded and served.

Flaws in Current React SSR Architecture

  • The whole JS is to be loaded before the hydration and then components become interactive.
  • The data, such as API calls, is to be already ready on the server side as it won’t wait.
  • All components are to be hydrated prior to interaction with them.

React 18 SSR Architecture

Unlikely the current SSR which follows the “whole or none” approach, React 18 brings revolutionary changes in this regard by enabling HTML streaming, selective hydration and lazy loading components in SSR.

1. HTML Streaming

Wrapping any component with <suspense></suspense> will tell React to not wait for that component. Instead, it will start streaming HTML and when the data for that code, wrapped in Suspense, is ready on the server, an additional HTML will be sent into the same stream with minimal inline JS to place at the right place.

For streaming HTML on server, renderToPipeableStream API to be used instead of renderToString.

It will help overcome the issue of having complete data before rendering anything.

2. Selective Hydration and Lazy Loading on SSR

React’s Lazy feature enables lazy loading on the client-side, i.e. only JS of the required component will be loaded instead of the whole app.

Before using selective hydration, you have to opt-in for the new createRoot API.

Wrapping products tells React to unblock the rest of the components from streaming, and there is no wait for the whole JS to be loaded before hydration.

While the components are being hydrated, React will prioritise the most urgent part of the screen based on the user interaction.

import { lazy } from 'react';

const Products = lazy(() => import('./products.js'));

<Suspense fallback={<Spinner />}>
<Products />
</Suspense>

New APIs

1. Concurrent Rendering

  • startTransition API

Working with huge amounts of data and updating large states often slow down or freezes the UI.

The startTransition API comes to the rescue as it transitions state updates into not-urgent ones and performs complex tasks in the background. The user experience remains seamless.

import { useTransition, startTransition } from 'react';// Urgent: Show what was typed
setInputValue(input);

// Mark any state updates inside as transitions
startTransition(() => {
// Transition: Show the results
setSearchQuery(input);
});

It also provides useTransition hook to check the status of the transitions.

import { useTransition } from 'react';

const [isPending, startTransition] = useTransition();
{isPending && <Spinner />}
  • useDeferredValue
import { useDeferredValue } from "react";const deferredValue = useDeferredValue(text, { timeoutMs: 2000 });

The hook returns the deferred version of the passed value. It is commonly used to keep the interface responsive when you have something that renders immediately based on user input and something that needs to wait for a data fetch.

2. useId

useId is an API for generating unique IDs on both the client and server while avoiding hydration mismatches.

const id = useId();<input type="checkbox" name="react" id={id} />

Improvements in Suspense with SuspenseList

SuspenseList comes into action when multiple components need data fetching and unpredictable orders.

SuspenseList only operates on the closest Suspense and SuspenseList components below it. It does not search for boundaries deeper than one level. However, it is possible to nest multiple SuspenseList components in each other to build grids.

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

With SuspenseList, a component will not be shown until the previous one has fetched data and is ready.

It takes two props: revealOrder (forwards, backwards, together) defines the order in which the listed components to reveal and tail (collapsed, hidden) dictates how unloaded items in a SuspenseList is shown.

Summary

Given the architectural changes, React 18 is bringing, the React community is excited. It will deliver better UI, UX, CX, and more reliable and efficient web apps are just around the corner.

Stay tuned!

Let's connect on LinkedIn.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store