Read this before using React 18...
Josiah Ayres
Posted on May 21, 2022
React 18 was released on 29 March 2022.
After reading the How to Upgrade to React 18 blog post these were my key takeaway points:
- Updates to Client Rendering APIs, one minor change to apply in your main
index.tsx
file:
// 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" />);
- Updates to TypeScript definitions. Developers must now explicitly list the children prop when defining props, for example:
interface MyButtonProps {
color: string;
children?: React.ReactNode;
}
The blog post explains the other changes and additions to React 18, however most React developers won't need to do anything.
This gave me confidence to jump right in to upgrading my application to React 18.
The Upgrade to 18
Straight away I noticed some weird behaviours:
Jumping into the console, I could see that my component "setup" useEffect
, with an empty dependency array was being triggered twice.
I was sure I hadn't changed the component code, and could not understand why I was seeing my component double rendering.
A quick Google search pointed to the <StrictMode />
component being the cause.
The (interim) fix
Changing my code from
import { StrictMode } from "react";
import * as ReactDOMClient from "react-dom/client";
import App from "./App";
const rootElement = document.getElementById("root");
const root = ReactDOMClient.createRoot(rootElement);
root.render(
<StrictMode>
<App />
</StrictMode>
);
to
import * as ReactDOMClient from "react-dom/client";
import App from "./App";
const rootElement = document.getElementById("root");
const root = ReactDOMClient.createRoot(rootElement);
root.render(
<App />
);
seemed to fix all the issues and double rendering of setup functions.
The cause
From the bottom end of the React 18 Upgrade Guide post they explain:
To demonstrate the development behavior you’ll see in Strict Mode with this feature, consider what happens when React mounts a new component. Without this change, when a component mounts, React creates the effects:
* React mounts the component. * Layout effects are created. * Effects are created.
With Strict Mode starting in React 18, whenever a component mounts in development, React will simulate immediately unmounting and remounting the component:
* React mounts the component. * Layout effects are created. * Effect effects are created. * React simulates effects being destroyed on a mounted component. * Layout effects are destroyed. * Effects are destroyed. * React simulates effects being re-created on a mounted component. * Layout effects are created * Effect setup code runs
On the second mount, React will restore the state from the first mount. This feature simulates user behavior such as a user tabbing away from a screen and back, ensuring that code will properly handle state restoration.
The proper fix
Adding in a return statement to the useEffect to handle the cleanup of the effect on unmount.
import { useEffect, useState } from "react";
const intervalInMs = 100;
const minuteInMs = 1000;
export const React18SafeTimer = () => {
const [timeInSeconds, setTime] = useState(0);
const [updateTimerInterval, setUpdateTimerInterval] = useState(0);
useEffect(() => {
console.log("React 18 Timer Setup");
const timerInterval = setInterval(
() => setTime((t) => t + intervalInMs / minuteInMs),
intervalInMs
);
setUpdateTimerInterval(timerInterval);
// THE FIX: Add next line to properly clean up useEffect
return () => clearInterval(timerInterval);
}, []);
const handleClearTimer = () => {
clearInterval(updateTimerInterval);
};
return (
<>
<p>seconds since page load: {timeInSeconds.toFixed(1)}</p>
<button onClick={handleClearTimer}>Stop Timer</button>
</>
);
};
See more at the Hooks API Reference page.
If you're having issues after upgrading to React 18, take a look at any of the following, as StrictMode changes around unmounting and remounting changes includes:
- componentDidMount
- componentWillUnmount
- useEffect
- useLayoutEffect
- useInsertionEffect
Why should I use StrictMode?
StrictMode is a (development mode) tool for highlighting potential problems in an application. It does not run in Production environment. Like Fragment, StrictMode does not render any visible UI. It activates additional checks and warnings for its descendants.
StrictMode currently helps with:
- Identifying components with unsafe lifecycles
- Warning about legacy string ref API usage
- Warning about deprecated findDOMNode usage
- Detecting unexpected side effects
- Detecting legacy context API
- Ensuring reusable state
- Additional functionality will be added with future releases of React.
Why has React made these changes?
In the future, we’d like to add a feature that allows React to add and remove sections of the UI while preserving state. For example, when a user tabs away from a screen and back, React should be able to immediately show the previous screen. To do this, React support remounting trees using the same component state used before unmounting.
This feature will give React better performance out-of-the-box, but requires components to be resilient to effects being mounted and destroyed multiple times. Most effects will work without any changes, but some effects do not properly clean up subscriptions in the destroy callback, or implicitly assume they are only mounted or destroyed once.
To help surface these issues, React 18 introduces a new development-only check to Strict Mode. This new check will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount.
Additional discussions on GitHub that gave a lot more context into the "why" behind the changes to StrictMode:
Summary
React 18 introduces changes which can lead to an unexpected Developer experience and messier console logs with same messages being triggered twice. It also introduces different application behaviour when running in development mode vs production, which is arguably undesirable.
While these changes have been done for a good reason, and should be embraced rather than ignored, React devs need to know what to expect before upgrading. Hopefully this guide helps save you time and answers your questions.
Posted on May 21, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 11, 2024