Claude CodeでReact 19をアップグレード — codemodの取りこぼしをSkillで拾う
React 19の公式codemodが拾う変更と拾わない変更を仕分け、defaultProps・テストのact・型定義の残りをClaude Codeに洗い出させる手順です。再利用できるSKILL.mdの例つき。
React 19へのアップグレードは、公式のcodemodを先に当て、残った箇所をClaude Codeに洗い出させる順番で進めると作業しやすくなります。codemodは一部の変更しか書き換えません。defaultProps や forwardRef、テストまわりの警告は、実行後もコードに残ります。この記事では、codemodの対象範囲を表で仕分けたうえで、取りこぼしを探す手順を SKILL.md に切り出して使い回す形を紹介します。
先に18.3へ上げ、codemodを当てる順番
React 19のアップグレードガイドは、最初に react@18.3 へ上げることを勧めています。18.3は18.2と同一の中身で、React 19で必要になる変更について非推奨の警告を出すリリースです。警告の一覧がそのまま作業リストになります。
アップグレードの流れ
- 1
18.3に上げて警告を集める
テストとビルドを回し、非推奨の警告をログに残します。
- 2
React 19とcodemodを入れる
reactとreact-domを^19.0.0に、TypeScriptなら@types/reactと@types/react-domも^19.0.0にします。 - 3
公式codemodを当てる
JavaScriptの変更はレシピ、型の変更は
types-react-codemodです。 - 4
取りこぼしをClaude Codeで探す
codemodの対象外を洗い出し、1件ずつ直します。
- 5
テストとビルドで確かめる
手順1と同じ条件で回し、警告が減ったことを確認します。
依存を更新するコマンドは、ガイドに次の形で載っています。
npm install --save-exact react@^19.0.0 react-dom@^19.0.0
npm install --save-exact @types/react@^19.0.0 @types/react-dom@^19.0.0codemodの実行は2系統あります。作業ツリーをきれいにしてから、1つずつ当てます。
git status --short # 何も出ない状態から始める
npx codemod@latest react/19/migration-recipe
npx types-react-codemod@latest preset-19 ./src
git diff --statreact-codemod を直接使うより codemod コマンドのほうが、実行が速く、複雑な書き換えに対応し、TypeScriptへの対応も良いとされています。
codemodが書き換えるもの、残すもの
レシピ react/19/migration-recipe が実行するのは次の5つです。replace-reactdom-render、replace-string-ref、replace-act-import、replace-use-form-state、prop-types-typescript。ガイドは、この中にTypeScriptの変更は含まれないと明記しています。
ガイドの項目ごとに、codemodの記載の有無を並べると次のとおりです。
| 変更 | codemod | 人が見る範囲 |
|---|---|---|
ReactDOM.render / hydrate / unmountComponentAtNode | codemodあり(replace-reactdom-render) | 人が見る範囲書き換え後のルート生成の位置 |
| 文字列ref | codemodあり(replace-string-ref) | 人が見る範囲クラスコンポーネントの置換結果 |
react-dom/test-utils の act | codemodあり(replace-act-import) | 人が見る範囲act 以外のtest-utils関数 |
propTypes | codemodあり(prop-types-typescript) | 人が見る範囲型に置き換わったかの確認 |
関数コンポーネントの defaultProps | codemodガイドに記載なし | 人が見る範囲デフォルト引数への手作業の書き換え |
findDOMNode、Legacy Context | codemodガイドに記載なし | 人が見る範囲refやcontextTypeへの手作業の書き換え |
型定義(useRef・JSX名前空間など) | codemodtypes-react-codemod | 人が見る範囲サードパーティの型拡張 |
表の下3行が、Claude Codeに探させる領域です。
defaultPropsとpropTypesの残りを探す
propTypes は、React 19でチェックが外れ、書いてあっても黙って無視されます。エラーが出ないので、残っていても気づけません。defaultProps は関数コンポーネントでは使えなくなり、ES6のデフォルト引数に置き換える必要があります。クラスコンポーネントは代替がないため、defaultProps をそのまま使えます。
ガイドの例では、次のように書き換えます。
// After
interface Props {
text?: string;
}
function Heading({text = 'Hello, world!'}: Props) {
return <h1>{text}</h1>;
}grepで候補を出すだけなら、Claude Codeを使わなくても済みます。ただし「関数コンポーネントかクラスか」の判定は、ファイルを開かないと決まりません。ここをClaude Codeに任せます。
grep -rnE "\.defaultProps\s*=" src
grep -rn "propTypes" src
grep -rn "forwardRef" srcforwardRef は、ガイドの破壊的変更の一覧に入っていません。React 19は ref をpropとして受け取れる仕様になり、代わりに element.ref へのアクセスが非推奨になりました。forwardRef の書き換えは必須ではなく、element.ref を読んでいる箇所の方が先に直す対象です。
テストのactとサードパーティの型定義を洗い出す
テストでは、react-dom/test-utils から act を読んでいると、ReactDOMTestUtils.act が非推奨で React.act を使うよう促す警告が出ます。replace-act-import が import {act} from 'react' に直します。
注意するのは act 以外の関数です。react-dom/test-utils のそれ以外の関数は削除され、呼ぶとエラーになります。codemodはこの部分を代替しないため、残りをgrepで探します。
grep -rnE "react-dom/test-utils|react-test-renderer" src testsreact-test-renderer はReact 19で非推奨になり、警告を出して並行レンダリングに切り替わります。ガイドの移行先は @testing-library/react と @testing-library/react-native です。
型まわりでは、types-react-codemod が拾えない自前の型拡張が残ります。たとえばグローバルな JSX 名前空間を拡張している global.d.ts は、declare module "react" で包む形に変わりました。モジュール名は tsconfig.json の jsx 設定で決まります。
"jsx": "react-jsx"ならreact/jsx-runtime"jsx": "react-jsxdev"ならreact/jsx-dev-runtime"jsx": "react"または"preserve"ならreact
ほかに、useRef が引数必須になった点、refコールバックが暗黙のreturnを返すと型エラーになる点があります。前者は refobject-defaults、後者は no-implicit-ref-callback-return として、プリセットに含まれます。ReactElement の props は、型引数なしだと any ではなく unknown になりました。要素のpropsを直接読んでいたコードは、tsc が型エラーとして示します。useReducer も、型引数にreducer全体の型を渡す書き方ができなくなり、型引数なしで書くのが新しい推奨です。どちらもエラー箇所が tsc --noEmit の出力に並ぶので、そのままClaude Codeへ渡せます。
サードパーティのライブラリ側が内部APIに依存していると、ガイドは「アップグレードを妨げる可能性がある」と書いています。依存パッケージの対応状況は、npm ls react の出力で見つかる警告から手がかりを得ます。
grepでは見つからない実行時の変更
ここまでの残件は、文字列で検索できる書き方でした。React 19には、コードを書き換えなくても挙動が変わる項目もあります。スキルの手順2では、こちらを「要確認」の欄に並べさせます。
- レンダー中のエラー: 再スローされなくなりました。Error Boundaryで捕まらないエラーは
window.reportError、捕まったエラーはconsole.errorに出ます。本番のエラー報告が再スローに依存している場合は、createRootとhydrateRootのオプションonUncaughtErrorとonCaughtErrorに報告処理を移します - 新しいJSX変換: 古い変換のままだと、React 19では警告が出ます。ガイドは、多くの環境で有効になっているため影響は小さいとしています
- Strict Mode: 開発中の二重レンダリングで、
useMemoとuseCallbackが1回目の結果を再利用します。初期マウントではrefコールバックも二重に呼ばれます - UMDビルドの廃止: スクリプトタグで読み込んでいるページは、ESMベースのCDNに切り替えます
これらはテストを回して初めて分かる種類の変化です。Claude Codeに「エラー報告の呼び出し元を探して」と頼み、報告処理が onUncaughtError に載っているかを読ませると、見落としを減らせます。
取りこぼし探しをSKILL.mdに切り出す
ここまでのgrepと判定を毎回プロンプトで打つのは手間です。.claude/skills/ に置いておけば、同じ手順を別のリポジトリや次の担当者でも使えます。
プロジェクトのスキルは .claude/skills/<名前>/SKILL.md に置きます。リポジトリにコミットすれば、チームの全員が同じ手順を使えます。例えば次のような形です。
---
name: react19-leftovers
description: React 19のcodemod実行後に残る非対応箇所を洗い出す。codemodを当てた後、またはReact 19移行の残件確認を頼まれたときに使う。
disable-model-invocation: true
argument-hint: "[対象ディレクトリ]"
allowed-tools: Bash(grep *) Bash(git diff *) Bash(npx tsc *)
---
## 作業ツリーの状態
!`git status --short`
## 手順
対象は $ARGUMENTS 。未指定なら src 。
1. 次のパターンを検索し、ファイルごとに分類する。
- `.defaultProps =` : 関数コンポーネントとクラスを区別
- `propTypes`
- `forwardRef` と `element.ref`
- `react-dom/test-utils` と `react-test-renderer`
- `declare global` と `namespace JSX`
2. 分類ごとに、直す・直さない・要確認を表にして示す。
3. 直すと決めたものだけ編集し、`npx tsc --noEmit` を実行する。
4. 実行結果を要約し、直さなかった箇所の理由を残す。このSKILL.mdで押さえている点は3つです。
disable-model-invocation: true: 副作用のある作業なので、手動の/react19-leftoversでだけ動かす!で始まるコマンド:git status --shortの出力がスキル本文に展開され、作業ツリーの状態を最初から渡せる$ARGUMENTS: 呼び出し時に打った文字列が入り、対象ディレクトリを切り替えられる
allowed-tools は、そのスキルを呼び出したターンだけ有効です。次のメッセージを送ると解除されます。スキルの本文を長くしすぎない点も重要で、公式は500行以下を勧めています。個別の判定基準が増えたら、checklist.md のような補助ファイルに分けて SKILL.md から参照させます。
編集の途中でも、スキルの変更は同じセッション内で反映されます。再起動は不要です。
個人用に ~/.claude/skills/ へ置いた場合は、全プロジェクトで使えます。ただしCoworkやクラウドのセッションでは読み込まれません。複数のリポジトリで同じ移行をするなら、個人用に置くか、プラグインにまとめて配る選択肢があります。
つまずきやすい箇所
落とし穴
警告が消えても移行は終わっていない
propTypes はReact 19では黙って無視されるため、テストの失敗として表に出ません。grepで残りを数え、0件になるまで確認します。
落とし穴
codemodが全体を書き換えて差分が膨らむ
レシピとtypes-react-codemodを同じコミットで当てると、レビューしづらくなります。codemodごとに分けてコミットすれば、問題の切り分けも楽です。
落とし穴
クラスコンポーネントのdefaultPropsまで消してしまう
クラスコンポーネントの defaultProps は、React 19でも使えます。スキルの手順で「関数かクラスか」を区別させておけば、不要な書き換えを避けられます。
CLAUDE.mdに残しておく規約
移行の途中でセッションが変わっても、方針がぶれないよう、リポジトリの CLAUDE.md に短く書いておきます。
## React 19移行の方針
- 関数コンポーネントの defaultProps はデフォルト引数に置き換える
- クラスコンポーネントの defaultProps はそのまま残す
- 変更は codemod ごとに別コミットにする
- 最後に npx tsc --noEmit とテストを全件実行して、結果を貼るテストの回し方は、Claude CodeでJest 30へアップグレードと同じ型で使えます。ベースラインの通過数を先に記録して、移行後に減っていないかを比べる進め方です。スキルのfrontmatter全体はClaude Code Skillsのfrontmatter全フィールドにまとめています。ルーターの型生成と合わせて検証する場合はClaude CodeでReact Router v7アプリを開発するも参考になります。
完了の判定
移行が終わったかどうかは、次の4点で判断します。
- 18.3で出ていた非推奨の警告が、React 19でも出ない
- 上のgrepの結果が、意図して残した箇所(クラスの
defaultPropsなど)だけになる npx tsc --noEmitが通る- テストの通過数が、移行前のベースラインから減っていない
最後の1点は、テストの削除やスキップが紛れていないかを git diff で確かめるところまで含めます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。