「フォームの送信中フラグを useState で持って、try/catch で囲んで、エラーも別の state に入れて……」——React でずっと書いてきたこの定型文、React 19 なら数行で消えますforwardRef も不要になり、メタデータもコンポーネントから直接書けるようになりました。

この記事では、React 19 で正式に使えるようになった機能のうち、実務でそのまま効く8つをコピペできるコード付きで紹介します。

共通するテーマは一つ——これまで useEffect や外部ライブラリで書いていた処理が、React 本体に吸収されたということです。バージョンは執筆時点で stable な React 19 系(本サイトも react 19.2 系で稼働)を前提にしています。

独学に限界を感じたら

プログラミング教室がおすすめ。挫折せず最短で実力を伸ばせます。

PR

1. use() —— Promise とContextを「条件付きで」読める

use() は Promise や Context を render 中に読むための新 API です。Promise を渡すと、解決するまで React が自動で Suspend します(<Suspense> のフォールバックが出る)。

データ取得のたびに useEffect + useState でローディング state を組む必要がありません。

import { use, Suspense } from "react";

function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
  // promise が解決するまで React が Suspend する(ローディング state 不要)
  const comments = use(commentsPromise);
  return comments.map((c) => <p key={c.id}>{c.text}</p>);
}

function Page({ commentsPromise }) {
  return (
    <Suspense fallback={<p>読み込み中…</p>}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  );
}

さらに use() は従来のフックと違い、条件分岐や早期 return の後でも呼べます。これが地味に効きます。

import { use } from "react";

function Heading({ show, themeContext }) {
  if (!show) return null;
  // 早期 return の後でも OK(useContext はここでは呼べなかった)
  const theme = use(themeContext);
  return <h1 className={theme}>Hello</h1>;
}

2. useActionState —— フォームの「結果・エラー・送信中」を1本化

フォーム処理の定型文をまるごと引き受けるフックです。react から import します(react-dom ではない点に注意)。アクション関数は (前の状態, formData) を受け取り、返り値が次の状態になります。返り値は [状態, formAction, isPending] の3つです。

import { useActionState } from "react";

async function updateName(_prev: State, formData: FormData): Promise<State> {
  const name = String(formData.get("name"));
  const error = await save(name); // 失敗時にメッセージを返す想定
  if (error) return { error };
  return { error: null };
}

function NameForm() {
  const [state, formAction, isPending] = useActionState(updateName, { error: null });

  return (
    <form action={formAction}>
      <input name="name" />
      <button disabled={isPending}>{isPending ? "保存中…" : "保存"}</button>
      {state.error && <p role="alert">{state.error}</p>}
    </form>
  );
}

useStateisSubmitting も、try/catch のエラー state も、送信ハンドラの preventDefault も要りません。<form action={formAction}> に渡すだけで完結します。

3. useFormStatus —— 送信ボタンだけを賢くする

「送信ボタンを別コンポーネントに切り出したいけど、親フォームの送信中状態を props でバケツリレーしたくない」——そんなときの useFormStatusreact-dom から import し、親 <form>内側にあるコンポーネントで呼ぶと、その form の送信状態を直接読めます。

import { useFormStatus } from "react-dom";

// このコンポーネントは <form> の子孫として描画する必要がある
function SubmitButton() {
  const { pending } = useFormStatus();
  return <button type="submit" disabled={pending}>{pending ? "送信中…" : "送信"}</button>;
}

function CommentForm({ action }) {
  return (
    <form action={action}>
      <textarea name="body" />
      <SubmitButton /> {/* props 不要で親の pending を拾う */}
    </form>
  );
}

注意点は一つだけ。useFormStatus はその form をレンダーしているコンポーネント自身では読めません。必ず子コンポーネントに切り出してください。

4. useOptimistic —— 「即座に反映、失敗したら自動で戻す」

チャットや「いいね」のように、サーバー応答を待たずに UI を先に更新したいケース。useOptimistic は楽観的な値を作り、非同期処理が完了(または失敗)すると自動で本来の state に戻します。手動のロールバック処理が消えます。

import { useOptimistic } from "react";

function Thread({ messages, sendMessage }) {
  const [optimistic, addOptimistic] = useOptimistic(
    messages,
    (state, newText: string) => [...state, { text: newText, sending: true }],
  );

  async function formAction(formData: FormData) {
    const text = String(formData.get("text"));
    addOptimistic(text);     // まず画面へ即反映
    await sendMessage(text); // 完了/失敗で自動的に messages に同期
  }

  return (
    <>
      {optimistic.map((m, i) => (
        <div key={i}>{m.text} {m.sending && <small>(送信中)</small>}</div>
      ))}
      <form action={formAction}><input name="text" /></form>
    </>
  );
}

5. ref はただの prop に —— forwardRef が不要に

React 19 では関数コンポーネントで ref普通の prop として受け取れるようになりました。ラッパーコンポーネントを作るたびに forwardRef で囲む儀式は不要です。

// React 18 まで:forwardRef で囲む必要があった
const Input18 = forwardRef<HTMLInputElement, Props>((props, ref) => (
  <input ref={ref} {...props} />
));

// React 19:ref は分割代入で受け取るだけ
function Input({ ref, ...props }: Props & { ref?: React.Ref<HTMLInputElement> }) {
  return <input ref={ref} {...props} />;
}

あわせて、ref コールバックがクリーンアップ関数を返せるようになりました。useEffect を使わずに DOM 要素の後片付けができます。

<input
  ref={(node) => {
    if (!node) return;
    const onResize = () => { /* ... */ };
    window.addEventListener("resize", onResize);
    // React 19:この cleanup がアンマウント時に呼ばれる
    return () => window.removeEventListener("resize", onResize);
  }}
/>

6. メタデータをコンポーネントから直接書く

<title> / <meta> / <link> をコンポーネントのどこに書いても、React が自動で <head>巻き上げてくれます。react-helmet のような外部ライブラリなしで、ページごとの SEO メタを宣言できます。

function Article({ post }) {
  return (
    <article>
      {/* 自動的に <head> へ移動する */}
      <title>{post.title}</title>
      <meta name="description" content={post.excerpt} />
      <link rel="canonical" href={post.url} />

      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.body }} />
    </article>
  );
}

※ Next.js の App Router を使っている場合は、従来どおり metadata エクスポートや generateMetadata が推奨されます。この機能は素の React やライブラリ内での利用が主役です。

7. Actions —— useTransition が非同期を面倒みる

useTransition非同期関数(Action)に対応しました。startTransitionasync 関数を渡せば、その完了までを自動でペンディング状態として扱ってくれます。フォーム以外の任意のボタン操作でも「送信中」表現を素直に書けます。

import { useTransition, useState } from "react";

function SaveButton({ save }: { save: () => Promise<void> }) {
  const [isPending, startTransition] = useTransition();
  const [error, setError] = useState<string | null>(null);

  function onClick() {
    startTransition(async () => {
      try {
        await save(); // 完了まで isPending が true
      } catch (e) {
        setError("保存に失敗しました");
      }
    });
  }

  return (
    <>
      <button onClick={onClick} disabled={isPending}>{isPending ? "保存中…" : "保存"}</button>
      {error && <p role="alert">{error}</p>}
    </>
  );
}

8. おまけ:React Compiler で useMemo / useCallback を卒業する

React 19 世代とともに実用段階に入った React Compiler は、再レンダーの最適化(メモ化)をビルド時に自動で挿入します。これまで「念のため」で撒いていた useMemo / useCallback の多くが不要になります。

// これまで:手でメモ化を撒いていた
const sorted = useMemo(() => items.sort(byName), [items]);
const handleClick = useCallback(() => onSelect(id), [id, onSelect]);

// React Compiler 導入後:素直に書くだけでコンパイラが最適化する
const sorted = items.slice().sort(byName);
const handleClick = () => onSelect(id);

導入は Babel / SWC プラグインとして段階的に有効化できます。まずは既存プロジェクトに乗せてみて、体感の再レンダー削減とバンドルの変化を確認するのがおすすめです。「最適化のためのフック」を書く時代は終わりつつあります

まとめ:React が「定型文」を引き取り始めた

React 19 の新機能に共通するのは、これまで開発者が手書きしていたお決まりの処理を、React 本体が引き取ったという点です。

フォームの pending 管理は useActionStateuseFormStatus へ、楽観的更新は useOptimistic へ、ref のラッピングと最適化は不要に。結果として useEffect の出番が確実に減ります。

  • データ取得・Context は use()(条件付きで呼べる)
  • フォームは useActionState + useFormStatus + <form action>
  • 楽観的 UI は useOptimistic(失敗時の巻き戻しも自動)
  • forwardRef は不要、ref はただの prop(コールバックは cleanup も返せる)
  • メタデータはコンポーネントから直接、最適化は React Compiler へ委譲

まずは一番効果が大きいフォーム周りから useActionState に置き換えてみてください。コード量の減り方に驚くはずです。