12.1 Action
React 19では、Transition内で実行される非同期関数をActionとして扱い、pending、エラー、楽観的更新などを構成しやすくなった。
const [isPending, startTransition] = useTransition();
function handleSubmit() {
startTransition(async () => {
const result = await updateProfile(form);
if (!result.ok) {
setError(result.error);
return;
}
setMessage("保存しました");
});
}
12.2 <form action>
async function saveUser(formData: FormData) {
const name = String(formData.get("name") ?? "");
await api.saveUser({ name });
}
function UserForm() {
return (
<form action={saveUser}>
<input name="name" />
<SubmitButton />
</form>
);
}
関数Actionが成功すると、非制御フォームの入力が自動的にリセットされる。
12.3 useActionState
type FormState = {
error: string | null;
success: boolean;
};
async function save(
previousState: FormState,
formData: FormData
): Promise<FormState> {
const name = String(formData.get("name") ?? "").trim();
if (!name) {
return {
error: "名前を入力してください。",
success: false,
};
}
await api.save({ name });
return {
error: null,
success: true,
};
}
function Form() {
const [state, action, isPending] = useActionState(save, {
error: null,
success: false,
});
return (
<form action={action}>
<input name="name" />
<button disabled={isPending}>
{isPending ? "保存中..." : "保存"}
</button>
{state.error && <p role="alert">{state.error}</p>}
</form>
);
}
12.4 useFormStatus
フォーム配下の送信状態を読み取る。
function SubmitButton() {
const { pending, data, method, action } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? "送信中..." : "送信"}
</button>
);
}
useFormStatusを呼ぶコンポーネントは、対象のformの子孫でなければならない。
12.5 useOptimistic
サーバー応答を待たず、成功したと仮定したUIを表示する。
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(current, text: string) => [
...current,
{
id: `optimistic-${Date.now()}`,
text,
sending: true,
},
]
);
async function sendMessage(formData: FormData) {
const text = String(formData.get("message") ?? "");
addOptimisticMessage(text);
await api.sendMessage(text);
}
失敗時にどう戻すか、重複送信をどう扱うか、サーバー側の最終結果とどう整合させるかを設計する。
12.6 フォーム設計原則
- HTMLの標準機能を活用する
nameを付ける- labelを関連付ける
- サーバー側でも検証する
- pending中の多重送信を防ぐ
- フィールドエラーと全体エラーを分ける
- 楽観的更新は取り消し可能性を考える
- Actionを認可境界として信頼しない