「フォームの送信中フラグを useState で持って、try/catch で囲んで、エラーも別の state に入れて……」——React でずっと書いてきたこの定型文、React 19 なら数行で消えます。forwardRef も不要になり、メタデータもコンポーネントから直接書けるようになりました。
この記事では、React 19 で正式に使えるようになった機能のうち、実務でそのまま効く8つをコピペできるコード付きで紹介します。
共通するテーマは一つ——これまで useEffect や外部ライブラリで書いていた処理が、React 本体に吸収されたということです。バージョンは執筆時点で stable な React 19 系(本サイトも react 19.2 系で稼働)を前提にしています。
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>
);
}useState の isSubmitting も、try/catch のエラー state も、送信ハンドラの preventDefault も要りません。<form action={formAction}> に渡すだけで完結します。
3. useFormStatus —— 送信ボタンだけを賢くする
「送信ボタンを別コンポーネントに切り出したいけど、親フォームの送信中状態を props でバケツリレーしたくない」——そんなときの useFormStatus。react-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)に対応しました。startTransition に async 関数を渡せば、その完了までを自動でペンディング状態として扱ってくれます。フォーム以外の任意のボタン操作でも「送信中」表現を素直に書けます。
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 管理は useActionState と useFormStatus へ、楽観的更新は useOptimistic へ、ref のラッピングと最適化は不要に。結果として useEffect の出番が確実に減ります。
- データ取得・Context は use()(条件付きで呼べる)
- フォームは useActionState + useFormStatus + <form action>
- 楽観的 UI は useOptimistic(失敗時の巻き戻しも自動)
- forwardRef は不要、ref はただの prop(コールバックは cleanup も返せる)
- メタデータはコンポーネントから直接、最適化は React Compiler へ委譲
まずは一番効果が大きいフォーム周りから useActionState に置き換えてみてください。コード量の減り方に驚くはずです。





