React 19の新機能 - Actions、use、楽観的更新と移行時の注意点

8分 で読める | 2025.12.02

React 19.0
公式ドキュメント

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をまとめる
useActionStateActionの結果stateとpending状態を扱う
useFormStatus親formの送信状態を子componentから読む
useOptimistic応答前の仮stateを表示する
usePromiseまたは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

useFormStatusreact-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のuseRefReactElement型、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してから広げるのが安全です。

参考リソース

← 一覧に戻る
PR
PR
PR
PR