問題点
package.json の React を 18 に上げたばかりであれば、ブラウザのコンソールに警告が表示されているはずです。アプリ自体は読み込まれますが、「レガシーモード」で動作しています。これは、React 18 の優れた機能を利用できていないことを意味します。古い API を使い続けると、複雑なイベントハンドラー内での再レンダリングを最大 50% 削減できる「自動バッチ処理(Automatic Batching)」などのメリットを享受できません。
表示されているエラーメッセージは以下の通りです:
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 では、アプリケーションルートの初期化方法が変更されました。従来の ReactDOM.render API は正式に非推奨となりました。新しいコンカレントレンダラーを有効にし、UI の応答性を向上させるために、createRoot に置き換えられています。
エラーの解決方法
この修正には、エントリーファイル(通常は index.js または main.jsx)の 2 分程度の書き換えが必要です。以下の手順に従って、アプリを最新の状態に更新しましょう。
ステップ 1:インポートの更新
最初の変更点は、インポート元です。React 17 では react-dom からインポートしていましたが、React 18 では createRoot 関数は特定のサブパスに配置されています。
以前のインポート:
import ReactDOM from 'react-dom';
新しいインポート:
import { createRoot } from 'react-dom/client';
ステップ 2:createRoot への移行
以前はコンポーネントと DOM 要素を 1 つの関数に渡していましたが、React 18 では「ルートの作成」と「コンポーネントのレンダリング」の 2 つのステップに分かれます。
以前の方法(React 17):
const container = document.getElementById('root');
ReactDOM.render(<App />, container);
新しい方法(React 18):
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);
ステップ 3:TypeScript の Null チェックへの対応
TypeScript ユーザーの場合、コンテナが null になる可能性があるというエラーが表示されることがあります。ルートを初期化する前に、要素が存在することを確認する必要があります。これにより、index.html に期待される 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 />);
}
要素が確実に存在するとわかっている場合は、非 null アサーション演算子を使用します:
const root = createRoot(document.getElementById('root')!);
サーバーサイドレンダリング(SSR)の変更点
独自の Express 設定で SSR を使用していますか?その場合、ReactDOM.hydrate も廃止予定です。ハイドレーションのロジックを新しいレンダラーと互換性を持たせるために、react-dom/client パッケージの hydrateRoot に切り替える必要があります。
変更前:
ReactDOM.hydrate(<App />, document.getElementById('root'));
変更後:
import { hydrateRoot } from 'react-dom/client';
const container = document.getElementById('root');
const root = hydrateRoot(container, <App />);
修正の確認
コードを書き換えた後、以下の 3 つの項目を確認してください:
- コンソールの確認: デベロッパーツール(F12)を開きます。リフレッシュ後、警告がすぐに消えているはずです。
- React DevTools: 拡張機能で、アプリが「Concurrent Mode」を使用していると表示されるようになります。
- ステートのテスト: ステートの更新を素早く何度か実行してみてください。新しい自動バッチ処理ロジックにより、パフォーマンスが向上していることに気づくはずです。
よくある落とし穴
1. createRoot のネスト
コンポーネントの中で createRoot を呼び出さないでください。これは必ずエントリーファイルに記述する必要があります。関数コンポーネント内で呼び出すと、再レンダリングのたびにアプリツリー全体が再作成され、パフォーマンスが著しく低下し、ローカルステートが失われます。
2. /client サフィックスの忘れ
インポート時に /client サフィックスを忘れると、関数が undefined になる可能性が高いです。これは、「createRoot is not a function」エラーが発生する最も一般的な原因です。
3. 削除されたコールバック
ReactDOM.render のコールバック(第 3 引数)は削除されました。マウント後にコードを実行する必要がある場合は、最上位の App コンポーネント内で useEffect フックを使用してください。
現代的な代替手段:
function App() {
useEffect(() => {
console.log('アプリが正常にマウントされました!');
}, []);
return <div>私の React 18 アプリ</div>;
}

