Fix: ReactDOM.render is Deprecated in React 18

beginner⚛️ React2026-04-25| React 18.x, Node.js 14.x or higher, Webpack/Vite/Create React App

Error Message

Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17.
#react#react18#web-dev#javascript

The Problem

If you've just bumped your package.json to React 18, your browser console is likely screaming at you. The app still loads, but it is running in "Legacy Mode." This means you are missing out on React 18’s best features. By sticking with the old API, you lose benefits like Automatic Batching, which can reduce re-renders by up to 50% in complex event handlers.

Here is the exact message you're seeing:

Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17.

React 18 changed how the application root initializes. The old ReactDOM.render API is officially deprecated. It has been replaced by createRoot to enable the new concurrent renderer and improve UI responsiveness.

How to Fix the Error

Fixing this requires a quick two-minute refactor of your entry file—usually index.js or main.jsx. Follow these steps to get your app back on track.

Step 1: Update your Imports

The first change is where you get your tools. In React 17, you imported from react-dom. In React 18, the createRoot function lives in a specific sub-path.

Old Import:

import ReactDOM from 'react-dom';

New Import:

import { createRoot } from 'react-dom/client';

Step 2: Transition to createRoot

Previously, we passed the component and the DOM element into one function. React 18 splits this into two steps: creating the root and then rendering the component.

The Old Way (React 17):

const container = document.getElementById('root');
ReactDOM.render(<App />, container);

The New Way (React 18):

const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);

Step 3: Solving the TypeScript Null Check

TypeScript users might see an error stating that the container could be null. You must verify the element exists before initializing the root. This prevents runtime crashes if your index.html is missing the expected ID.

import { createRoot } from 'react-dom/client';
import App from './App';

const container = document.getElementById('root');

if (container) {
  const root = createRoot(container);
  root.render(<App />);
}

If you are 100% sure the element exists, use the non-null assertion operator:

const root = createRoot(document.getElementById('root')!);

Server-Side Rendering (SSR) Changes

Are you using SSR with a custom Express setup? If so, ReactDOM.hydrate is also on its way out. You should switch to hydrateRoot from the react-dom/client package to keep your hydration logic compatible with the new renderer.

Before:

ReactDOM.hydrate(<App />, document.getElementById('root'));

After:

import { hydrateRoot } from 'react-dom/client';

const container = document.getElementById('root');
const root = hydrateRoot(container, <App />);

Verifying the Fix

After swapping the code, perform these three quick checks:

  • Console Check: Open DevTools (F12). The warning should vanish immediately upon refresh.
  • React DevTools: The extension should now label your app as using "Concurrent Mode."
  • State Test: Trigger a few rapid state updates. You should notice smoother performance due to the new automatic batching logic.

Common Pitfalls

1. Nesting createRoot

Never call createRoot inside a component. It belongs strictly in your entry file. Calling it inside a functional component will recreate the entire app tree on every re-render, destroying performance and wiping out your local state.

2. The Missing /client Suffix

If you forget the /client suffix in your import, the function will likely be undefined. This is the most common reason for "createRoot is not a function" errors.

3. The Removed Callback

The ReactDOM.render callback (the third argument) is gone. If you need to run code after the initial mount, use a useEffect hook inside your top-level App component instead.

The Modern Alternative:

function App() {
  useEffect(() => {
    console.log('App successfully mounted!');
  }, []);

  return <div>My React 18 App</div>;
}

Related Error Notes