Introduction
Since updates frequently include modifications that changes features completely or even eliminate certain features and add others, some developers may find it difficult to transition between different versions of libraries. It is better to use the most recent versions of libraries to get the best performance possible.
You can either create a new React project or reinstall React in an existing project to migrate from React 17 to React 18.
This article will discuss what React 18 is, issues with React 17, new features in React 18, and reasons why you should use the most recent version. You should be familiar with JavaScript, React, and NPM to follow along with this article.
Step we'll cover:
What is React 18
Before we look into “What’s new in React 18”, what does React 18 mean? Any stable version of the React library from 18.0.0 upwards but not including 19.0.0 is known as React 18.
The creation of React 18 introduced concurrent rendering in React applications. React has been taking care of DOM rendering and giving developers tools to control and track component lifecycle. With some new capabilities, React 18 can now adapt the rendering process to suit client devices.
Upgrading to React 18
The React community offers a variety of installation options. To install React 18 in your application, you can use the CDN URL as the source(src) in an HTML script tag.
<!-- Load React. -->
<!-- Note: when deploying, replace "development.js" with "production.min.js". -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<!-- Load our React component. -->
<script src="app.js"></script>
</body>
By executing the following commands in a terminal of your working directory, you can upgrade or install React 18 using NPM or Yarn for single-page and bundled applications. For NPM:
npm install react react-dom
For Yarn:
yarn add react react-dom
The above commands will automatically detect and install or upgrade the most recent React and React DOM versions in your development environment.
Issues with React 17
The React community has noticed some issues or problems with the library which require improvement. **React 18 and higher versions wouldn't need to be released if React 17 was functioning flawlessly.
According to the changelog of React 18.0.0, the following issues with React 17 or earlier were addressed:
- Render throws an error if
undefined
is returned: When a component returns a value ofundefined
, the application will break.
The application displays the following error:
You will also notice the error below in your console:
setState
of unmounted component gives a warning: In an attempt to update the state of an unmounted component, React might warn you of a memory leak.
Strict mode console log suppression: From community feedback, it was noticed that the suppression of the console log message when using Strict Mode creates confusion since only one shows instead of two.
Memory consumption: React 17 and earlier had issues with memory leaks, especially in unmounted components.
What changed in React 18
More emphasis is made on application concurrency in React 18. This idea includes functions such as Automatic Batching, Transition, and Suspense, as well as APIs like createRoot, hydrateRoot, renderToPipeableStream, and renderToReadableStream. It also includes hooks such as useId, useTransition, useDeferredValue, useSyncExternalStore, and useInsertionEffect, as well as updates on Strict Mode and the deprecation of ReactDOM.render
and renderToString
.
Let’s take a deeper look at these changes 👀:
Client Rendering
You might want to keep an eye out for the console warning listed below after an upgrade:
If you continue to use the ReactDOM.render()
API supported in React 17; you will see this warning. Typically, we import a component and render it inside a div element with the id=app".
import ReactDOM from 'react-dom';
import App from 'App';
const app = document.getElementById('app');
ReactDOM.render(<App />, app);
In React 18, as in the following code sample, we use the createRoot()
API imported from "react-dom/client":
import { createRoot } from 'react-dom/client';
import App from 'App';
const app = document.getElementById('app');
// create a root
const root = createRoot(app);
//render app to root
root.render(<App />);
Hydration
React 17 used the ReactDOM.hydrate()
API for rendering with hydration, as in the following code sample:
import * as ReactDOM from 'react-dom';
import App from 'App';
const app = document.getElementById('app');
// Render with hydration.
ReactDOM.hydrate(<App tab="home" />, app);
In React 18, hydration uses the hydrateRoot()
API imported from “react-dom/client” and doesn’t require a separate render()
method as in the code snippet below:
import {hydrateRoot} from 'react-dom/client';
import App from 'App';
const app = document.getElementById('app');
const root = hydrateRoot(app, <App tab="home" />);
Render Callback
You could pass a callback function when rendering the root component so that it would execute after the component renders or updates.
In the render method of React 17, you could pass a callback function as the third argument, as in the code snippet below:
import * as ReactDOM from 'react-dom';
import App from 'App';
const app = document.getElementById('app');
ReactDOM.render(app, <App tab="home" />, function() {
// Called after initial render or any update.
console.log('Rendered or Updated').
});
The callback function is not allowed in React 18 because it affects the application's runtime with progressive or partial hydration. Instead, you could use a ref callback, setTimeout
, or requestIdleCallback
on the root element, as in the code example below:
import {createRoot} from 'react-dom/client';
function App({ callback }) {
// Callback will be called when the div is first created.
return (
<div ref={callback}>
<h1>Hello World</h1>
</div>
);
}
const app = document.getElementById("root");
const root = createRoot(app);
root.render(<App callback={() => console.log("Rendered or Updated")} />);
Automatic Batching
State updates were only batch processed in React event handlers before version 17. Therefore any state updates made outside of event handlers resulted in a re-render, which required React to perform additional background tasks. For instance:
const handleClick = () => {
setFirstState(“1”);
setSecondState(“2”);
}
React will only re-render in the code snippet above once all the states have been changed at the end of the event callback function. Otherwise is the case for state updates in promises, native events or outside React event handlers. For instance:
fetch(‘https://api.com’).then(() => {
setFirstState("1");
setSecondState("2");
})
//OR
setTimeout(() => {
setFirstState("1");
setSecondState("2");
})
In the code snippet above, React will re-render for each state update.
The createRoot()
API in React 18 enables batching all state updates, regardless of where they happen in the application. React then re-renders the page after all state-updates.
Since this is a breaking change, you can stop automatic batching using the flushSync()
API.
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setFirstState("1");
});
flushSync(() => {
setSecondState("2");
});
}
In the code snippet above, each instance of flushSync()
updates state and allows React to re-render.
Transitions
You can use Transitions to distinguish between resources that need urgent/immediate state updates and those that do not.
The functionality of the search bar is a good example. While a user types the search word, you might want to display visual feedback. However, you don't want the search to start until the user has finished typing.
import { startTransition } from 'react';
// Urgent: Show what was currently typed
setSearchCurrentValue(input);
startTransition(() => {
// Not-urgent: Show what was finally typed
setSearchFinalValue(input);
});
In the code snippet, instead of using setTimeout()
which will delay state updates, we used startTransition()
to monitor the state update. setSearchCurrentValue()
only updates the state that is concerned with the feedback we want the user to get immediately, the setSearchFinalValue()
updates the state we want to use to eventually make the search when the user has finished typing.
Unlike setTimeout
, startTransition
updates can be interrupted, can track a pending update, and it executes immediately.
Dropped support for Internet Explorer
The React community has also dropped support for Internet Explorer which means that only browser feature up until React 17 will work on Internet Explorer. Modern browser features such as multitasks, Promise
, Object.assign
or Symbol
won’t be pollyfilled in Internet Explorer.
Benefits of React 18 over React 17
Even after learning the differences between React 17 and React 18, you may still be unsure about switching to React 18 or sticking with React 17.
A new version won't be appreciated if it doesn't provide more benefits over previous ones.
Concurrency is one of React 18's main advantages. It is a brand-new concept, not a feature, that enables React apps running on React 18 and higher to optimize their performance on client devices.
By clearing out background tasks on unmount, React 18 enhances memory management and lowers the danger of memory leaks.
Conclusion
You should be able to update your React version and refactor your codebases to seamlessly use React 18 after reading this tutorial.
To get the most recent information on changes and new releases, you should also keep a close eye on the React library changelogs for updates and stay in touch with the React community.