ReactDOM.render was deprecated in React 18 and will be removed in React 19. This warning appears when using the legacy rendering API instead of the new createRoot API. Migrating to createRoot unlocks concurrent rendering features and ensures compatibility with future React versions.
This deprecation warning indicates you're using the legacy ReactDOM.render() API introduced in React 16, which is incompatible with React 18's concurrent rendering architecture. When React 18 was released in March 2022, the rendering system was redesigned to support concurrent features like automatic batching, transitions, and Suspense improvements. The old ReactDOM.render() creates a "legacy root" that runs in React 17 compatibility mode, preventing your app from accessing any React 18 features. The new createRoot API from 'react-dom/client' creates a modern root that enables the concurrent renderer. This renderer can interrupt, pause, resume, or abandon rendering work, making your UI more responsive. While ReactDOM.render() still works in React 18 with a warning, it will be completely removed in React 19, making migration mandatory. The warning message "your app will behave as if it's running React 17" means you're missing out on performance improvements, automatic batching of state updates, and the ability to use concurrent features that make React applications faster and more efficient.
Replace the legacy import with the new client-side rendering API:
// Before (React 17 and earlier)
import ReactDOM from 'react-dom';
// After (React 18+)
import { createRoot } from 'react-dom/client';For TypeScript projects, ensure you have @types/react-dom version 18.0.0 or later installed.
Update your entry point file (typically index.js, index.tsx, or main.tsx):
// Before (React 17)
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// After (React 18)
import { createRoot } from 'react-dom/client';
import App from './App';
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);The key difference is that createRoot() returns a root object, and you call render() on that root rather than passing the container directly to ReactDOM.render().
If your app uses SSR, replace ReactDOM.hydrate() with hydrateRoot():
// Before (React 17)
import ReactDOM from 'react-dom';
ReactDOM.hydrate(
<App />,
document.getElementById('root')
);
// After (React 18)
import { hydrateRoot } from 'react-dom/client';
const container = document.getElementById('root');
hydrateRoot(container, <App />);Note that hydrateRoot doesn't return a root object like createRoot does - it immediately hydrates the container.
Replace ReactDOM.render in test files with the new API:
// Before
import ReactDOM from 'react-dom';
test('renders component', () => {
const container = document.createElement('div');
ReactDOM.render(<MyComponent />, container);
expect(container.textContent).toBe('Hello');
});
// After
import { createRoot } from 'react-dom/client';
test('renders component', () => {
const container = document.createElement('div');
const root = createRoot(container);
root.render(<MyComponent />);
expect(container.textContent).toBe('Hello');
});If using React Testing Library, upgrade to version 13+ which uses createRoot automatically.
Add proper null checks since getElementById can return null:
// TypeScript-safe version
const container = document.getElementById('root');
if (!container) {
throw new Error('Failed to find the root element');
}
const root = createRoot(container);
root.render(<App />);This prevents TypeScript errors and runtime crashes if the DOM element doesn't exist.
If you ever unmount the root (common in tests or dynamic SPAs):
// Before
ReactDOM.unmountComponentAtNode(container);
// After
root.unmount();The unmount method is now called on the root instance rather than the container.
Ensure all React packages are version 18.0.0 or later:
npm list react react-dom
# or
yarn list react react-domIf versions are mismatched, update both packages:
npm install react@latest react-dom@latest
# or
yarn add react@latest react-dom@latestMismatched versions (e.g., React 18 with react-dom 17) will cause errors.
Concurrent Rendering Benefits: After migrating to createRoot, your app gains access to concurrent features like automatic batching (multiple setState calls in event handlers are batched into a single re-render), useTransition for marking updates as non-urgent, and useDeferredValue for deferring expensive computations. These features significantly improve perceived performance without code changes.
Backward Compatibility: If you're maintaining a library that needs to support both React 17 and 18, you can conditionally use the appropriate API:
import * as ReactDOM from 'react-dom';
const render = (app, container) => {
if (ReactDOM.createRoot) {
// React 18
const root = ReactDOM.createRoot(container);
root.render(app);
return root;
} else {
// React 17 and earlier
ReactDOM.render(app, container);
}
};Callback Removal: ReactDOM.render() accepted an optional callback as a third argument that fired after rendering. createRoot's render() does not support callbacks. Use useEffect in a component or flushSync for synchronous updates instead.
Strict Mode Behavior: React 18's Strict Mode intentionally double-invokes effects in development to help find bugs. This is unrelated to the createRoot migration but may cause confusion when migrating older codebases.
Third-Party Library Issues: Some libraries (Chakra UI, Material-UI, testing libraries) had their own wrappers around ReactDOM.render. Check their migration guides for React 18 compatibility or update to versions that support createRoot natively.
Create React App: CRA version 5.0.1+ automatically uses createRoot. If you're on an older CRA version, either upgrade or manually eject and update the entry point.
Prevention: When starting new projects, use create-react-app@latest, Next.js 12.2+, or Vite's React template, all of which default to createRoot. Always refer to the official React documentation rather than outdated tutorials.
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite