React 19は2024年12月5日にstableとして公開されました。中心となる変更は、非同期のデータ更新をActionとして扱い、pending、error、optimistic UI、form送信をReactの更新処理へ統合したことです。
先に押さえたいこと
記事情報: 2025年12月2日初出。React 19公式release記事とupgrade guideを2026年7月25日に再確認しています。
React 19の「Action」は、すべてがserverで動くことを意味しません。startTransitionへ渡すasync functionや、<form action={fn}>へ渡すfunctionもActionとして扱われます。Server Functionを呼ぶ仕組みは使用するframeworkの実装と設定を確認してください。
主なAPIの役割は次のとおりです。
| API | 担当すること |
|---|---|
| Action | 非同期の更新とtransitionをまとめる |
useActionState | Actionの結果stateとpending状態を扱う |
useFormStatus | 親formの送信状態を子componentから読む |
useOptimistic | 応答前の仮stateを表示する |
use | PromiseまたはContextをrender中に読む |
どのAPIも認証、入力検証、通信の再試行を自動で完成させるものではありません。
Actionsとform
React 19では<form>のaction propへfunctionを渡せます。そのfunctionはFormDataを受け取り、非同期処理を実行できます。
async function updateProfile(formData: FormData): Promise<void> {
const displayName = String(formData.get("displayName") ?? "");
if (displayName.trim().length === 0) {
return;
}
await saveProfile({ displayName });
}
function ProfileForm() {
return (
<form action={updateProfile}>
<input name="displayName" />
<SubmitButton />
</form>
);
}
Actionが成功すると、uncontrolled form fieldは自動的にresetされます。入力を残したい場合やcontrolled inputを使う場合は、期待する挙動をtestしてください。
client側Actionから直接databaseへ接続できるわけではありません。API routeを呼ぶのか、frameworkのServer Functionを使うのか、browserとserverの境界を明示します。server側では毎回、認証・認可・validationを行います。
useActionState
useActionStateは、Actionが返した値をstateとして保持し、pending状態も返します。Canary版でReactDOM.useFormStateと呼ばれていたAPIは、stable版ではReact.useActionStateへrenameされました。
import { useActionState } from "react";
type State = { error: string | null };
const initialState: State = { error: null };
async function submit(
_previousState: State,
formData: FormData,
): Promise<State> {
const title = String(formData.get("title") ?? "");
if (!title.trim()) {
return { error: "タイトルが必要です" };
}
await createPost({ title });
return { error: null };
}
function PostForm() {
const [state, formAction, pending] = useActionState(
submit,
initialState,
);
return (
<form action={formAction}>
<input name="title" disabled={pending} />
<button disabled={pending}>保存</button>
{state.error && <p role="alert">{state.error}</p>}
</form>
);
}
Actionの第1引数にはprevious stateが入るため、通常のform Actionとfunction signatureが異なります。移行時にFormDataを第1引数として扱わないよう注意してください。
useFormStatus
useFormStatusはreact-domからimportし、componentが属する親<form>のstatusを読みます。buttonを再利用componentへ分けるときに、pendingをpropで渡さず取得できます。
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? "送信中…" : "送信"}
</button>
);
}
このhookは同じcomponentがrenderするformではなく、祖先のformを参照します。SubmitButtonを<form>の外に置くと期待したstatusを読めません。複数formがある画面では、DOM上の所属をtestします。
useOptimistic
useOptimisticはserver応答を待つ間、成功後を仮定したstateを表示します。Actionが完了するとbase stateへ戻るため、親から確定済みdataが渡される流れを設計します。
import { useOptimistic } from "react";
function LikeButton({
likes,
saveLike,
}: {
likes: number;
saveLike: () => Promise<void>;
}) {
const [optimisticLikes, addOptimisticLike] = useOptimistic(
likes,
(current) => current + 1,
);
async function action() {
addOptimisticLike(null);
await saveLike();
}
return (
<form action={action}>
<button>いいね {optimisticLikes}</button>
</form>
);
}
楽観的更新は失敗しても安全に戻せる操作に向きます。決済や在庫確保のように、成功していない状態を確定表示すると誤解を招く処理では、pending表示を優先してください。二重送信、順序逆転、失敗messageもtest対象です。
useでPromiseとContextを読む
useはrender中にPromiseまたはContextを読みます。Promiseがpendingなら近くのSuspense boundaryへ処理が移り、rejectされた場合はError Boundaryで扱います。
import { Suspense, use } from "react";
function UserName({
userPromise,
}: {
userPromise: Promise<{ name: string }>;
}) {
const user = use(userPromise);
return <p>{user.name}</p>;
}
function Page({ userPromise }: { userPromise: Promise<{ name: string }> }) {
return (
<Suspense fallback={<p>読み込み中…</p>}>
<UserName userPromise={userPromise} />
</Suspense>
);
}
render中に毎回新しいPromiseを作ると、不要なsuspendやwarningにつながります。Promiseの生成場所はframeworkのdata fetching方式と合わせます。
use(Context)は条件分岐内でも呼べる点がuseContextと異なりますが、通常のHooksと混同して任意の場所から呼べるわけではありません。componentまたはHookのrender中で使います。
そのほかの変更
React 19ではfunction componentでrefをpropとして受けられ、forwardRefを必要としない形へ移行できます。またcomponent内の<title>、<meta>、<link>をdocument metadataとして扱い、stylesheet、preload、preinitなどresource loadingのAPIも追加されました。
これらはframework側にも既存のmetadata・resource管理がある場合があります。Next.jsなどを使う場合は、React単体のAPIだけでなくframeworkの推奨方法と競合しないか確認してください。
React 18からの移行
公式guideは、まずReact 18.3へ上げ、19で問題になるdeprecated APIのwarningを確認する手順を勧めています。React 19では新しいJSX transformが必要です。
npm install --save-exact react@^19.0.0 react-dom@^19.0.0
npm install --save-dev --save-exact \
@types/react@^19.0.0 @types/react-dom@^19.0.0
公式codemodもありますが、機械変換後は必ず差分とtestを確認します。
npx codemod@latest react/19/migration-recipe
特に確認する項目は、古いReactDOM.render、string ref、actのimport、TypeScriptのuseRef・ReactElement型、ref callbackのreturn、旧JSX transformです。libraryを公開している場合はpeer dependencyと型定義の対応範囲も決めます。
導入判断
Actions系APIは、非同期更新で個別に管理していたpending・error・optimistic stateを一定のpatternへまとめます。ただしAPI通信やserver境界をなくすものではありません。使用frameworkの対応状況、progressive enhancement、error boundary、cache更新の方法まで含めて設計してください。
既存画面を一度に書き換える必要はありません。新しいformから試し、成功・validation error・network error・二重送信をtestしてから広げるのが安全です。