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

> Encountering build issues when upgrading to React 18 and React DOM 18 is common, especially with legacy APIs. Learn how to fix them by adopting the concurrent root API and updating your transpiler.

- Repository: [Meta/react](https://github.com/facebook/react)
- Tags: how-to-guide
- Published: 2026-02-21

---

**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`](https://github.com/facebook/react/blob/main/CHANGELOG.md) and [`packages/react/README.md`](https://github.com/facebook/react/blob/main/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`](https://github.com/facebook/react/blob/main/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`](https://github.com/facebook/react/blob/main/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`](https://github.com/facebook/react/blob/main/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`](https://github.com/facebook/react/blob/main/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`](https://github.com/facebook/react/blob/main/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`](https://github.com/facebook/react/blob/main/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`](https://github.com/facebook/react/blob/main/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`](https://github.com/facebook/react/blob/main/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`](https://github.com/facebook/react/blob/main/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:

```javascript
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:

```javascript
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`:

```javascript
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:

```javascript
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:

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

```

### Running the Official Codemod

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

```bash
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`.