React v18 migration
React v16 to v18 Migration Guide (with Best Practice Code)
Updates to Client Rendering APIs Migration
The new root API also enables the new concurrent renderer, which allows you to opt-into concurrent features.
// 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" />);We’ve also changed unmountComponentAtNode to root.unmount:
// Before
unmountComponentAtNode(container);
// After
root.unmount();We’ve also removed the callback from render, since it usually does not have the expected result when using Suspense:
// 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 />);How to Upgrade for the New JSX Transform
CRA: v4.0.0 +
eslint-plugin-react
{
// ...
"rules": {
// ...
"react/jsx-uses-react": "off",
"react/react-in-jsx-scope": "off"
}
}custom babel
@babel/plugin-transform-react-jsx
# for npm users
npm update @babel/core @babel/plugin-transform-react-jsx
# for yarn users
yarn upgrade @babel/core @babel/plugin-transform-react-jsx@babel/preset-react
# for npm users
npm update @babel/core @babel/preset-react
# for yarn users
yarn upgrade @babel/core @babel/preset-reactbabel config
// If you are using @babel/preset-react
{
"presets": [
["@babel/preset-react", {
"runtime": "automatic"
}]
]
}
// If you're using @babel/plugin-transform-react-jsx
{
"plugins": [
["@babel/plugin-transform-react-jsx", {
"runtime": "automatic"
}]
]
}Bulk-Removing Unnecessary Imports After Upgrading to v17
react-codemod
// general react
import React from 'react';
function App() {
return <h1>Hello World</h1>;
}
// transpile;
function App() {
return <h1>Hello World</h1>;
}// custom hook
import React from 'react';
function App() {
const [text, setText] = React.useState('Hello World');
return <h1>{text}</h1>;
}
import { useState } from 'react';
function App() {
const [text, setText] = useState('Hello World');
return <h1>{text}</h1>;
}Trouble Shooting
Click event using window.addEventListener is fired prematurely
export function useOutsideClick({
callback,
}: UseOutsideClickProps) {
const [element, setElement] = useState<HTMLElement>();
const ref = useCallback((el) => setElement(el), []);
useEffect(() => {
const handler = (event) => {
if (element && !element.contains(event.target)) {
callback(event);
}
}
if (element) {
document.addEventListener('click', handler);
}
return () => {
document.removeEventListener('click', handler);
};
}, [element, callback]);
return ref;
}
// Actual usage example
const [isPreviewTypeListVisible, setPreviewTypeListVisible] = useState(false);
// Before
useEffect(() => {
const handleDocumentClick = () => isPreviewTypeListVisible && setPreviewTypeListVisible(false);
document.addEventListener('click', handleDocumentClick);
return () => document.removeEventListener('click', handleDocumentClick);
}, [isPreviewTypeListVisible]);
// After
const ref = useOutsideClick({
callback: () => setPreviewTypeListVisible(false),
});
<div
ref={ref}
className={cx(styles.container, className)}
>
<button
type="button"
onClick={() => setPreviewTypeListVisible(!isPreviewTypeListVisible)}
>
</div>Property 'createRoot' does not exist on type '@types/react-dom/index")'. ts(2339)
- Check the react-dom version. You need to install v18 or higher.
- Even after installing v18 or higher, an issue can occur where an external library references a different React version.
- This happens when the library specifies the React version as a peerDependency.
- In that case, pin the version using resolutions in package.json.
"resolutions": {
...,
"react": "18.2.0"
}TypeError: Cannot read properties of null (reading 'useMemo')' error Redux in my react redux
- Install react-redux version 8 or higher.
- Reference: TypeError: Cannot read properties of null (reading 'useMemo')
- Reference: Overload 1 of 2, '(props: ProviderProps
| Readonly<ProviderProps >): Provider ', - For now, since updating react-redux also requires updating redux, this is temporarily handled with ts-ignore.
<QueryClientProvider client={queryClient}>
{/* @ts-ignore */}
<Provider store={store}>
</Provider>
</QueryClientProvider>
'ReactNode' is not assignable to type 'React.ReactNode'
- This is caused by an external library dependency. As above, pin the version using resolutions.
- Reference: React18 : Type{} is not assignable to type 'ReactNode' 해결
"resolutions": {
"@types/react": "18.2.0"
},index.js:1 Error: createRoot(...): Target container is not a DOM element
- This occurs when the target element is not a DOM object.
// Operation error in useMemo, forwardRef
if (!element) return;No overload matches this call. Overload 1 of 2, '(props: ProviderProps | Readonly<ProviderProps>): Provider', gave the following error....
- Starting from React 18, children must be declared explicitly.
- Declaring children
// Option 1. Add the children type directly
interface Props {
isOpened: boolean;
handleCloseModal: () => void;
children: ReactNode;
}
// Option 2. Use the PropsWithChildren type provided by React
function Component(props: PropsWithChildren<Props>);- Reference 2 (why children was excluded from the default type in React 18):
- Because you cannot determine whether children exist and it is difficult to infer the type of children, it was excluded starting from version 18.
- Removal of implicit children
Issues caused by automatic batching
https://github.githubassets.com/favicon.ico
const { mutate } = usePostSettlementInfo({
onSuccess: () => {
if (path) { // Without wrapping in flushSync, automatic batching kicks in and the state doesn't update
...
}
},
});
const onSubmit = handleSubmit((data) => {
....
mutate(data);
});
const handleSave = async (path: string) => {
flushSync(() => {
setPath(path);
});
await onSubmit();
};Uncaught ReferenceError: React is not defined
// If you are using @babel/preset-react
{
"presets": [
["@babel/preset-react", {
"runtime": "automatic"
}]
]
}
// If you're using @babel/plugin-transform-react-jsx
{
"plugins": [
["@babel/plugin-transform-react-jsx", {
"runtime": "automatic"
}]
]
}defaultProps no longer supported
Starting from version 18.3, defaultProps is deprecated. (A warning is shown.)
We are currently on version 18.2, so please be careful not to add defaultProps going forward.
Reference: Stop using defaultProps! | Sophia Willows
https://sophiabits.com/favicon.ico
Library Issue
Switching to the react-helmet-async library
Cause
- Starting from React 17, componentWillMount, componentWillReceiveProps, and componentWillUpdate are no longer supported, and to keep using them you need to migrate via the UNSAFE_ prefix. Because react-helmet was using methods that are not supported in version 17, warnings were raised.
- Update on Async Rendering – React Blog
Improvement
- Switched to a library called react-helmet-async
- npm: react-helmet-async
- How to fix "componentWillMount has been renamed"

react-hook-form isDirty delay update
// Since the form data was reset after saving with react-hook-form, the state was set to true instead of false, resulting in a situation where the prompt message had to be handled (occurs from v18 onward)
<Prompt
when={isDirty && !submited}
message={() =>
`You have unsaved changes, sure you want to go to leave this page? `
}
/>Issue where the antd datepicker gets blocked when the input tag is focused in older versions
- Bug: antd datepicker isnt work on react and react-dom 18 versionSTATUS: UNCONFIRMED
- Library version upgrades
- antd: 4.16.2 → 5.1.0
- rc-picker: 2.5.12 → 3.7.4
- dayjs: 1.10.5 → 1.11.7

Jest Issue
Updating Jest after upgrading to React 18
-
Encountered the error TypeError: Cannot read property 'current' of undefined
-
GitHub - testing-library/react-hooks-testing-library: 🐏 Simple and complete React hooks testing utilities that encourage good testing practices. 'Change in how waitFor is used'
// Change in the declaration //// Before const { result, waitFor } = renderHook(() => useRewards(), { wrapper: <App />, }); //// After import { act, renderHook, waitFor } from '@testing-library/react'; // Add expect inside the waitFor statement //// Before await waitFor(() => result.current.isSuccess); //// After await waitFor(() => expect(result.current.isSuccess).toBe(true));userEvent click malfunction
- After upgrading to React 18, userEvent click stopped working and only fireEvent works (this will likely be resolved later, but for now you need to switch to fireEvent)
- Reference: React 18 support · testing-library/user-event · Discussion #945
- userEvent.click → fireEvent.click
// Before import userEvent from '@testing-library/user-event'; userEvent.click(button); // After import { render, screen, waitFor } from '@testing-library/react'; await waitFor(() => userEvent.click(button));