What's New In React v18

The latest version of React was launched some weeks back. With the new version, we saw some promising features and changes that were made. The changes range from automatic batching to new APIs.

The most promising advancement in the React 18 is that many of the features are built on top of Concurrent Renderer, a modification made behind the scenes that enables significant new capabilities. Concurrent React is an opt-in feature that is only active when you use a concurrent feature. However, we believe it will have a considerable influence on how users create apps.

Concurrency React

Concurrency is not a characteristic in and of itself. It's a new method that works behind the scenes to allow React to prepare many versions of your UI at the same time. Concurrency is an implementation detail that is valuable because of the functionality it facilitates. In its underlying implementation, React employs advanced mechanisms like priority queues and multilayer buffering. However, you will not find those ideas in our public APIs.

Concurrent React's rendering is interruptible, which is a significant feature. Before introducing any concurrent functionality, changes are displayed the same way they were in earlier versions of React — in a single, unbroken, synchronous transaction. When using synchronous rendering, once an update begins rendering, nothing can stop it until the user sees the result on the screen.

Automatic Batching

React uses batching to combine numerous state updates into a single re-render for improved efficiency. We batched changes solely inside React event handlers without automated batching. By default, updates within promises, setTimeout, native event handlers, or any other event were not batched in React. These changes were batched automatically with automated batching.

React uses batching to combine numerous state updates into a single re-render for improved efficiency. This improves performance by avoiding excessive re-rendering. It also prevents your component from presenting "half-finished" states in which just one state variable has been modified, which might lead to issues.

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

The above code snippet will run two times for React v17, once for each state update i.e., no batching is implemented.

The above code snippets will run one time for React v18, once at the end, hence implementing batching and increasing performance.

Transitions

Urgent updates represent immediate contact, such as typing, clicking, pushing, and so on. Meanwhile, Transition updates move the user interface from one view to another. Urgent updates, such as typing, clicking, or pressing require a quick reaction to match our intuition about how tangible things behave. Otherwise, they are "wrong." Transitions, on the other hand, are unique in that the user does not expect to see every intermediate value on the screen.

Updates wrapped in the startTransitions are treated as non-urgent and will be halted if more vital things, like clicks or key presses, arrive. If a user interrupts a transition (for example, by entering many letters in a row), React will discard any unfinished rendering work and render only the most recent update.

  • useTransition
    A hook to start transitions, including a value to track the pending state.
     
  • startTransition
    A method to start transitions when the hook cannot be used.
import {startTransition} from 'react';
 
// Urgent: Show what was typed
setInputValue(input);
 
// Mark any state updates inside as transitions
startTransition(() => {
  // Transition: Show the results
  setSearchQuery(input);
});

Suspense Feature

Suspense allows you to declare the loading status for a component tree section that isn't ready to be shown yet. Suspense elevates the "UI loading state" to first-class declarative status in the React programming model. This allows us to develop higher-level features on top of it. Suspense in React 18 works best when paired with the transition API. If you suspend during a transition, React will prevent already-visible content from being replaced with a fallback. Instead, React will wait for the render until enough data has been loaded to prevent a poor loading state.

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

Updates to Client Rendering API

The new client API ships with the following:

  • createRoot
    New method to create a root to render or unmount. Use it instead of ReactDOM.render. New features in React 18 don’t work without it.
     
  • hydrateRoot
    New method to hydrate a server-rendered application. Use it instead of ReactDOM.hydrate in conjunction with the new React DOM Server APIs. New features in React 18 don’t work without it.

Both createRoot and hydrateRoot now have a new option called onRecoverableError, if you wish to be informed when React recovers from rendering or hydration failures for logging purposes. React will utilize reportError by default, or console.error in older browsers.

1. Render Changed to createRoot

// Before
import { render } from 'react-dom';
const container = document.getElementById('app');
render(<App tab="home" />, container);
 
// After
import { createRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = createRoot(container); // createRoot(container!) if you use TypeScript
root.render(<App tab="home" />);

2. unmountComponentAtNode changed to root.unmount :

// Before
unmountComponentAtNode(container);
 
// After
root.unmount();

3. Removed the callback from the render:

// Before
const container = document.getElementById('app');
render(<App tab="home" />, container, () => {
  console.log('rendered');
});
 
// After
function AppWithCallbackAfterRender() {
  useEffect(() => {
    console.log('rendered');
  });
 
  return <App tab="home" />
}
 
const container = document.getElementById('app');
const root = createRoot(container);
root.render(<AppWithCallbackAfterRender />);

4. hydrate changed to hydrateRoot :

// Before
import { hydrate } from 'react-dom';
const container = document.getElementById('app');
hydrate(<App tab="home" />, container);
 
// After
import { hydrateRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = hydrateRoot(container, <App tab="home" />);
// Unlike with createRoot, you don't need a separate root.render() call here.

Updates to Server Rendering API

The new server API ships with the following methods:

  • renderToPipeableStream: for streaming in Node environments.
  • renderToReadableStream: for modern edge runtime environments, such as Deno and Cloudflare workers.

Note: The existing renderToString method keeps working but is discouraged.

Using the new API will now warn:

  • renderToNodeStream: Deprecated

Instead, for streaming in Node environments, use:

  • renderToPipeableStream: New

We’re also introducing a new API to support streaming SSR with Suspense for modern edge runtime environments, such as Deno and Cloudflare workers:

  • renderToReadableStream: New

The following APIs will continue working, but with limited support for Suspense:

  • renderToString: Limited
  • renderToStaticMarkup: Limited

Finally, this API will continue to work for rendering e-mails:

  • renderToStaticNodeStream

New Hooks

  1. useId
    useId is a new hook that allows you to generate unique IDs on both the client and the server while preventing hydration mismatches. It is most beneficial for component libraries that need to integrate with accessibility APIs that require unique IDs.
     
  2. useTransition
    useTransition and startTransition allow you to identify some state modifications as not urgent. Other state modifications are automatically marked urgent.
     
  3. useDefferedValue
    You may use useDeferredValue to postpone re-rendering a non-urgent section of the tree. This is similar to debouncing; however, it offers a few advantages.
     
  4. useSyncExternalStore
    useSyncExternalStore is a new hook that allows external stores to support concurrent readers by requiring synchronous updates to the store. It eliminates the requirement for useEffect when building subscriptions to external data sources and is recommended for any library that connects with the state that is not React-specific.
     
  5. useInsertionEffect
    The useInsertionEffect hook helps CSS-in-JS libraries to handle performance difficulties when injecting styles into the render. This hook will be executed after the DOM has been changed, but before layout effects have read the new layout.

Support ended for Internet Explorer

In this version, React removes support for Internet Explorer, which was decommissioned on June 15, 2022. This modification was made now because new features released in React 18 are designed with current browser technologies such as microtasks, which cannot be polyfilled satisfactorily in IE.

It is advised that you stick with React 17 if you need to support Internet Explorer.

How to upgrade to React v18?

To upgrade react and react-dom you can execute the following commands:

NPM

npm install react react-dom 

YARN

yarn add react react-dom 

Conclusion

In this article, we discussed some of the new features and changes introduced in React v18. To read you can visit the following:

  1. React v18 official blog
  2. Update to Client rendering APIs
  3. Update to Server rendering APIs



C# Corner
MVP Program Director