snow-mountain

React 18 — A Dive Into the Newest Version of React


Last updated on
ReactWeb Development

Summary (TL;DR): React 18 has recently rolled out and it offers new and interesting features like automatic batching, concurrency, transitions, new hooks, the ability to stop automatic batching with flushSync() and suspense API.

React 18 has been released recently, and there’s much excitement about it in the React community — partly because the last groundbreaking changes were in version 16 when hooks were introduced. While there have been other versions since, none have brought as significant, or as long-awaited changes as version 18. 

In this article, we’re going to dive into React 18, its recent updates, and use cases to help you master it. Let’s get started.

While You Are at It, Why Not Learn More About Using Drag and Drop in React Apps With sortableJS

Table of Contents

Major changes in React 18
Features in React 18
>> Automatic Batching
>> Stop Automatic Batching with flushSync()
>> Suspense API
>> Concurrency
>> Transitions
>> New Hooks

Major Changes in React 18

Before taking a look at the breaking changes in React, let’s see the latest difference, which is the new way of installing React 18.

With npm:

npm install react react-dom

After running the command above for the first time, you will receive a warning on your console saying:

ReactDOM.render is no longer supported in React 18. Use CreateRoot instead. Until you switch to the new API, your app will behave as if it’s running React 17. You can learn more here.

like the image below:

User-uploaded image: image.png

To fix this, change the ReactDOM.render method from:

// other imports
ReactDOM.render(
  <React.StrictMode>
    <App />
  <React.StrictMode>, 
  document.getElementById('root')
);

to: 

// other imports
const root = ReactDOM.createRoot(
  document.getElementById('root')
);

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

What is createRoot?

createRoot is a new root API that takes only one argument and replaces the render method in older React versions.createRoot provides React developers with concurrent features from React 18. 

Features in React 18

Automatic Batching

Batching occurs when React combines updates to state in hooks and event handlers. Batching happens behind the scenes in React, like the call stack and memory heap in Javascript or Memory safety and Goroutines in Golang. Developers do not have to know about them, but understanding them is good.

Before React 18, Batching only occurred for browser events and Hooks, which means that the state was updated only when async operations happened, and therefore event handlers weren’t batched.

Take a look at the example below to understand more about Batching in React 17:

import { useState } from "react";
const App = () => {
  const [count, setCount] = useState(0);
  const [count2, setCount2] = useState(1);
  console.log("clicked");
  const onClickHandler = () => {
    setCount(count + 1);
    setCount2(count2 * 2);
  };
  return (
    <div className="App">
      <button onClick={onClickHandler}>Click Me!</button>
      <p>Added Value:{count}</p>
      <p>Old value * 2:{count2}</p>
    </div>
  );
};
export default App;

The onclickHandler() function gets called from the example above whenever the user clicks on the button. If we check the console, we see the ‘clicked’ message logged once for the two state updates.

That is Batching in process. But what happens when we handle or execute updates to our state in a non-browser context?

import { useState } from "react";
const App = () => {
  const [count, setCount] = useState(0);
  const [count2, setCount2] = useState(1);
  console.log("clicked");

  const fetchSomething = () => {
    fetch("// fetch something from somewhere")
      .then(()=>{
      setCount(count + 1);
      setCount2(count2 * 2);
    })
  };
  return (
    <div className="App">
      <button onClick={fetchSomething}>Click Me!</button>
      <p>Added Value:{count}</p>
      <p>Old value * 2:{count2}</p>
    </div>
  );
};
export default App;

We noticed two separate’ clicked’ messages, and that’s because there were two separate re-renders for each state update.

In React 18, batching occurs whenever state updaters occur. Whether from promises, event handlers, timeouts, async operations etc.

Let’s add a timeout to our above example to see how batching works in React 18:

const handleTimeOutClick = () => {
    setTimeout(() => {
       setCount(count - 1);
      setCount2(count2 / 2); 
     });
   };
  return (
    <>
    <div className="App">
      <button onClick={fetchSomething}>Click Me!</button>
      <p>Added Value:{count}</p>
      <p>Old value * 2:{count2}</p>
    </div>
    <div>
      <button onClick={handleTimeOutClick}>timeout button</button>
    </div>
    </>
  );
};

Whenever we click on any of the two buttons, we notice that two messages are printed on the console even if two state updates occur. This is a good example showing how React batches all state updates.

Stop Automatic Batching With flushSync()

Now that we have looked at the awesomeness of automatic batching, there may come times when we do not want this feature. React provides us with a way to opt-out of it with flushSync().

Let’s take a look at an example below:

First we import flushSync() from react-dom:

import { flushSync } from 'react-dom';

Next we define our event handler and then place the state update inside the flushSync():

const handleTimeOutClick = () => {
    flushSync(() => {
      setCount(count + 1); // re-render occurs here
    });
    setCount2(count2 / 2); // re-render occurs here
  };

React will update the DOM first in flushSync() and then update the DOM again in the setCount.

Suspense API

Another new feature of React 18 is the introduction of the Suspense API on the server. If you have some data which you are getting from a server, you can wrap the part of the component in <Suspense> and then add a placeholder. Suspense tells React to render the placeholder while the data is fetching. Previously, Suspense was available using React.Lazy on the client-side.

Suspense also allows you to split your apps into small units that are independent of each other and won’t block your app’s rendering. This makes for faster loading of the content of your app.

Suspense also allows you to split your apps into small units that are independent of each other and which won’t block the rendering of your app. This makes for faster loading of your app content.

<Suspense fallback={<Spinner />}> //renders a loading spinner to the screen until the component has finished loading
  <RightColumn>
    <ProfileHeader />
  </RightColumn>
</Suspense>

The slow loading component will no longer hinder page load when you use Suspense.

Concurrency

Concurrency is a concept whereby two or more tasks are executed simultaneously. This means that an update, once initiated, cannot be interrupted. In React 17 and older, updates were in a single and synchronized sequence. 

In React 18, updates are no longer in a single sequence but can run almost asynchronously. React can now pause, initiate or abandon an already started render. This guarantees a seamless user experience and a consistent UI even when a render is paused or stopped.

Another advantage of this is the reusable state. React will now be able to remove parts from the user’s screen when they leave a tab and then add those parts back when the user comes back to the tab by reusing the previous state.

Please note that concurrency might not be considered a feature. Instead, it is a behind-the-scenes structure that enables React to produce many versions of your UI simultaneously. 

Transitions  

React introduced this update in React 18 to enable developers to prioritize updates. Now developers can differentiate between urgent updates and non-urgent updates.

Urgent updates like typing or clicking need an immediate response. Else they feel unnatural because we expect a button to respond when clicked.
Transition and non-urgent updates handle changes to the UI from one view to another as users don’t expect to see values immediately on the screen.

Take a look at the example below to understand how transitions work: First, we import startTransition() from react:

 import {startTransition} from 'react';

We then use it to differentiate between urgent and non-urgent updates by wrapping non-urgent updates in setCount:

// Urgent: Show what was typed
setInputValue(input);

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

Another way to start transitions is by using the useTransition hook.

New Hooks

React has also introduced some new hooks with React 18. React first introduced Hooks in React 16 to provide an alternative to state handling, lifecycle, and class-based components.

The new hooks introduced are:

  • useID: This hook helps in generating distinctive and special IDs in the server and client:
const Check=()=> {
  const id = useId();
  return (
    <div>
      <label htmlFor={id}>is this appealing to you?</label>
      <input id={id} type="checkbox"/>
    </div>
  );
};
  • UseTransition: UseTranstion is used to distinguish between urgent and non-urgent updates: 
function App() {
  const [isPending, startTransition] = useTransition();

  function handleClick() {
    startTransition(() => {
    // set a state
    })
  }

  return (
    <div>
      {isPending && <Spinner />} // renders a spinner while still pending
      
    </div>
  );
}

Conclusion

In this article, we learned about React 18, its recent changes, and use cases. We also looked at the new root API introduced in React 18, the createRoot API which provides concurrent features from React 18. We also looked at new APIs in React 18 like suspense API for rendering placeholders while data is fetched in a React application, and hooks like useTransitions and useID hook.