Do You Need to Expect Build Process Issues When Upgrading to React 18 and React DOM 18?

Yes, upgrading to React 18 and React DOM 18 commonly triggers build warnings and runtime deprecation notices if your codebase uses legacy APIs like ReactDOM.render, but these issues are resolved by adopting the new concurrent root API and updating your transpiler configuration.

Upgrading the facebook/react ecosystem from version 17 to React 18 introduces architectural changes centered on concurrent rendering. While the migration path is documented in CHANGELOG.md and packages/react/README.md, developers should anticipate specific build-time validations and runtime warnings related to deprecated entry points, JSX transform requirements, and strict mode behavior changes.

Common Build and Runtime Issues After Upgrading to React 18

Deprecated ReactDOM.render and the New Concurrent Root API

The most immediate issue you will encounter is the deprecation of ReactDOM.render. According to CHANGELOG.md (line 511), calling ReactDOM.render still functions but forces your application into "React 17 mode," disabling concurrent features and emitting console warnings.

The solution is to migrate to the new createRoot API found in packages/react-dom/client. This change is mandatory to enable automatic batching, transitions, and other concurrent features. For server-side rendered applications, replace ReactDOM.hydrate with hydrateRoot as documented in packages/react-dom/README.md.

New JSX Transform Requirements

React 18 expects the new JSX transform (automatic runtime) that was introduced in 2020. If your build pipeline still uses the classic React.createElement transform, you may encounter compile-time errors or warnings with modern bundlers.

As noted in packages/react/README.md, you must configure your build tool to use "react-jsx" or "react-jsxdev" instead of the classic runtime. This eliminates the need to import React in every file and ensures compatibility with React 18's internal optimizations.

Removed Legacy Test Utilities

Several legacy test utilities were removed in React 18, specifically ReactDOM.unstable_renderSubtreeIntoContainer (see CHANGELOG.md, line 527). Attempting to use these functions will result in build or test failures. The source code recommends migrating to @testing-library/react or using createRoot directly in your test setup.

StrictMode Double-Invocation Changes

React 18's StrictMode deliberately double-invokes certain functions to detect side effects. As documented in CHANGELOG.md (lines 537-538), components now mount, unmount, and remount automatically in development mode. Build processes that treat console warnings as errors may fail if your code produces warnings during this double-invocation cycle.

Automatic Batching Behavior Changes

Automatic batching, enabled by default in React 18, changes how state updates flush. Code that assumes immediate synchronous updates after a state change may behave differently, though this typically manifests as runtime behavior rather than build failure. For the rare cases requiring synchronous updates, use flushSync from react-dom as shown in CHANGELOG.md (line 537).

Essential Build Configuration Updates for React 18

Update Your Bundler and Transpiler Configuration

You must update your build toolchain to support the automatic JSX runtime:

  • Babel: Configure @babel/preset-react with { "runtime": "automatic" } in your babel.config.json or .babelrc.
  • SWC: Set "jsc.transform.react.runtime": "automatic" in your SWC configuration.
  • esbuild: Remove jsxFactory: "React.createElement" settings and ensure JSX files use the automatic runtime.

Upgrade TypeScript Definitions

React 18 ships with updated TypeScript definitions in packages/react and packages/react-dom. If you use @types/react, upgrade to @types/react@latest and @types/react-dom@latest, or rely on the bundled types if your package manager supports them.

Ensure Version Matching Between react and react-dom

React 18 requires exact version parity between react and react-dom. As documented in CHANGELOG.md (line 525), mismatched versions trigger a runtime error: "Throw error if react and react-dom versions don't match." Verify your package-lock.json or yarn.lock ensures both packages resolve to 18.x.x.

Step-by-Step Migration Code Examples

Migrating the Entry Point

The following example shows the legacy entry point that triggers warnings:

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.render(<App />, document.getElementById("root"));

Update to the React 18 concurrent root API:

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

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

Server-Side Hydration Migration

Replace the deprecated ReactDOM.hydrate with hydrateRoot:

import { hydrateRoot } from "react-dom/client";
import App from "./App";

hydrateRoot(document.getElementById("root"), <App />);

Handling Synchronous Updates

For the rare cases requiring immediate state flushing:

import { flushSync } from "react-dom";

function handleClick() {
  flushSync(() => {
    setCount(c => c + 1);
  });
  // State is guaranteed to be updated here
}

Babel Configuration for Automatic JSX

Update your Babel configuration to enable the automatic runtime:

{
  "presets": [
    [
      "@babel/preset-react",
      {
        "runtime": "automatic"
      }
    ]
  ]
}

Running the Official Codemod

Facebook provides an official codemod to automate the ReactDOM.render to createRoot migration:

npx react-codemod rename-react-dom-render src/

Summary

  • Yes, expect warnings: Upgrading to React 18 and React DOM 18 produces predictable deprecation warnings for ReactDOM.render and legacy test utilities.
  • Adopt createRoot: Migrate entry points to use createRoot (client) or hydrateRoot (server) to enable concurrent features and eliminate warnings.
  • Update build tools: Configure Babel, SWC, or esbuild to use the automatic JSX runtime ("automatic").
  • Match versions: Ensure react and react-dom share the exact 18.x.x version to avoid runtime errors.
  • Use the codemod: Run npx react-codemod rename-react-dom-render to automate the most common migration task.

Frequently Asked Questions

Will my existing React 17 code break immediately after upgrading to React 18?

No, most React 17 code will continue to function, but you will encounter deprecation warnings in development. Specifically, calls to ReactDOM.render will log warnings and run in a legacy compatibility mode that disables concurrent features. The application will not take advantage of React 18 improvements until you migrate to createRoot.

How do I fix the "ReactDOM.render is no longer supported" warning?

Update your application entry point to import createRoot from react-dom/client instead of react-dom. Replace ReactDOM.render(<App />, container) with const root = createRoot(container); root.render(<App />). For server-rendered apps, use hydrateRoot instead of ReactDOM.hydrate.

Do I need to change my Babel configuration when upgrading to React 18?

Yes, if you are not already using it, you should enable the automatic JSX runtime by setting "runtime": "automatic" in your @babel/preset-react configuration. This aligns with React 18's expectations and removes the requirement to import React in every JSX file. Without this update, you may encounter build warnings or larger bundle sizes.

Can I use React 18 without adopting the concurrent features?

Technically, yes, by continuing to use ReactDOM.render, but this is not recommended. Using the legacy entry point forces your application into React 17 compatibility mode, preventing access to automatic batching, transitions, and Suspense improvements. To fully utilize React 18, you must adopt the concurrent root API via createRoot.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →