New Features in React 18
React.js has come a long way since its launch in 2013. It almost immediately gained massive adoption due to its simplicity, un-opinionated setup, and great documentation. During the same time, Angular.js was near the peak of its popularity and was widely used by enterprises and startups. Compared to the alternatives, React offers a fresh perspective with a very snappy performance that has helped it to break the barriers of traditional frontend development.
The upcoming version of React (v18.0) includes some innovative features designed for scaling large-scale React applications. This article provides a high-level overview of the new features of React 18, along with examples and links to relevant materials.
Let’s get started.
What Is React?
React is an open source UI library for developing Single-Page Applications (SPAs). While it was first introduced by Facebook in 2013, the actual prototype work began in 2011, when the Facebook Ads team was trying to figure out how to manage the complexity of the platform (read more on that here). Since then, React.js has become the dominant library for frontend development, and it’s often the first choice for building web applications.
React competes with Angular in terms of development productivity and stability. React has inspired several other libraries (like Vue, Preact, and Solid.js) and many copycats.
React 18 is already released, and available on npm.
What’s New in React 18?
Let’s start by installing React and ReactDOM using npm
:
npm i react react-dom --save
New Root API
The React team decided to deprecate the way in which the initial root component was rendered due to its inflexible design. For example, when you needed to re-render the root component in earlier versions, you had to continue passing the container into the render even though it never changed. (To facilitate the adoption of React 18, the team also wanted to allow developers to use multiple versions of React in the same app.)
Before, the rendering process happened like this:
import App from "./App"
import { render } from "react-dom"
import "./index.css"
render(<App tab="home" />, document.getElementById("root"))
Now, with React 18, you need to do this:
index.js
import App from "./App"
import { createRoot } from "react-dom/client"
import "./index.css"
createRoot(document.getElementById("root")).render(<App />)
Instead of importing the render
function from react-dom
, it uses the createRoot
function from react-dom/client
(which accepts a root document element as a parameter). Then, with the resulting object, it calls the render
method and passes the root component for the initial render.
This is beneficial because you can save the resulting object in a variable and call the render method that allows you to pass a component with different props:
const app = createRoot(document.getElementById('root'));
app.render(<App theme="blue"/>);
app.render(<App theme="red"/>
You shouldn’t notice any changes in your application, but you’ll be able to opt-in to the new concurrent features now.
Suspense
For React 18, the React team made several improvements to the Suspense
component and fixed several bugs. Before this release, it was available as an experimental feature and was used successfully in many cases. Suspense provides a convenient way to mark a component as pending so that it will notify the parent component when it’s ready to render content or any other work.
Before Suspense, developers had to either fetch on render (i.e., first render a component, then fetch data) or fetch, then render (i.e., first fetch all data, then render). Both approaches had noticeable UI issues. With Suspense, you start rendering components just before you start loading the data they need, which leads to a smoother user experience and optimized UI performance.
The new version of React makes Suspense more resilient. For example, they fixed a quirk where suspended components would still render in the dom and trigger side-effects, even if they were behind placeholders. The new version also enables Suspense to run on the server side without throwing errors, which makes React more useful for server-side rendering and performance. Finally, it offers a new hook called useTransition
, which is one of the new concurrency features that we will explain in the next section.
Overall, this is the most practical and helpful feature of the new release.
Pending State Updates for Slow Renders
Until React 18, all updates were rendered urgently, meaning that there was no way to control the rendering priorities of a component. When a component was updating a state, it would trigger updates to the child components at the same time – which created performance issues. For example, you might have to re-render a big list of items or a deeply-nested component tree on every keystroke.
Now, React has a new hook called useTransition
. This allows developers to control how React triggers a pending state when it is rendering content behind the scenes. This is how you use it:
// New hook in React 18
const [isPending, startTransition] = useTransition()
const [val, setVal] = useState(0.1)
const data = slowComputation(val)
return (
<>
<Slider
defaultValue={0.1}
onChange={val => {
// Update the results, in a transition.
startTransition(() => {
setVal(val)
})
}}
/>
<ExpensiveComponentRender // Use the isPending flag to add a pending class.
className={isPending ? "pending" : "done"}
data={data}
/>
</>
)
By wrapping the onChange
handler of the Slider component with the startTransition
callback, you can update a user that something is happening in the background while he or she is still able to interact with the page. This is aimed at transitions for interactions that are slow to render (like updating a big list or requesting data from a backend), which differ from transitions that the user expects to happen instantly (like typing in an input or changing a slider). React 18 now gives you a way to control the handling of slow and fast interactions using a familiar hooks API.
State transitions are a great way to help make design systems or page components snappier and more responsive.
Performance Improvements
Using the createRoot API in React 18, all state updates will be automatically batched without explicitly calling the batch API.
This means that if you manage several useState
’s in the same component and update them in a callback, React will batch them together without re-rendering on every change:
import * as React from "react"
import "./App.css"
function fetchSomething() {
return new Promise(resolve => setTimeout(resolve, 1000))
}
function App() {
const [count, setCount] = React.useState(0)
const [flag, setFlag] = React.useState(false)
function handleClick() {
fetchSomething().then(() => {
// Automatically batched. Re–renders only once
setCount(c => c + 1)
setFlag(f => !f)
})
}
console.count("Render Count")
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "red" : "green" }}>{count}</h1>
</div>
)
}
export default App
In the example above, we triggered two setState
updates after we resolved the promise.
Prior to React 18, you would see the component render multiple times on each setState
when you clicked on the “Next” button:
Render Count: 1
Render Count: 2
Render Count: 3
>
Multiple Renders on Each State Change Prior to React 18
That does not happen with React 18. It will only render once, which is a very useful performance fix for those who care about reducing component rendering count.
Next Steps with React
If you want to learn more about the new React release, you can check out the following resources:
- The React team discusses React 18 – https://www.youtube.com/channel/UC1hOCRBN2mnXgN5reSoO3pQ/videos
- The official blog – https://reactjs.org/blog/2021/06/08/the-plan-for-react-18.html
- The React 18 working group – https://github.com/reactwg/react-18
The React 18 working group is an interesting online community that fosters discussions about the process of upgrading, pain points, and troubleshooting scenarios with existing libraries. If you have any questions regarding the new release, you can learn quite a few tricks and techniques by participating in these discussions.
You can also read more about frontend development on Livecycle’s blog.