Dev Study
← 解説「useActionState — フォームの結果と送信中を表示する」に戻る

サンプルコードで身につける: useActionState — フォームの結果と送信中を表示する

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1最小の useActionState

Server Action の戻り値を画面に表示する最小形です。action が返した文字列が result に入って再描画される、という「サーバーの結果が状態になる」流れをまず押さえましょう。

"use client";

import { useActionState } from "react";
import { sendMessage } from "./actions"; // 結果の文字列を返す Server Action

export default function MessageForm() {
  const [result, formAction] = useActionState(sendMessage, null);

  return (
    <form action={formAction}>
      <input name="message" />
      <button type="submit">送信</button>
      {result && <p>{result}</p>} {/* action の戻り値が表示される */}
    </form>
  );
}

2送信中はボタンを無効化する

3つ目の戻り値 isPending を使って、送信中の二重クリックを防ぎつつ「送信中...」と表示する例です。連打による二重送信はフォームの定番事故で、この数行がその対策の基本形になります。

"use client";

import { useActionState } from "react";
import { sendMessage } from "./actions";

export default function MessageForm() {
  const [result, formAction, isPending] = useActionState(sendMessage, null);

  return (
    <form action={formAction}>
      <textarea name="message" />
      <button disabled={isPending}>
        {isPending ? "送信中..." : "送信"}
      </button>
      {result && <p>{result}</p>}
    </form>
  );
}
// disabled={isPending} の1属性で二重送信を防げる。

3(前回の結果, formData) の2引数

useActionState 経由で呼ばれる Server Action は、第1引数に前回の結果、第2引数に FormData を受け取ります。レッスン本文の注意点である「FormData は2番目」を、action 側のコードで確認する例です。

// app/contact/actions.ts
"use server";

// useActionState 経由では (前回の結果, formData) の2引数になる
export async function sendMessage(
  prevResult: string | null, // 1番目: 前回 action が返した値
  formData: FormData // 2番目: 今回のフォーム入力
): Promise<string> {
  const message = String(formData.get("message") ?? "");
  if (message.trim() === "") {
    return "メッセージを入力してください";
  }
  return "送信しました: " + message;
}

// formData を1番目だと思い込むと get できずに悩むことになる。
// 「前回の結果が先、FormData が後」と覚える。

4状態が更新される流れを再現する

useActionState の「action の戻り値が次の状態になる」という更新サイクルを、プレーンな関数の繰り返し呼び出しで再現する例です。前回の状態を引き継ぎながら結果を上書きしていく感覚を、実行して確かめられます。

TypeScript

5ニュースレター登録フォーム

成功・失敗をオブジェクトの状態で返し、エラー表示・成功メッセージ・送信中の無効化まで揃えた実務水準のフォームです。useActionState の3つの戻り値が、フォーム UX に必要な要素にそれぞれ対応していることが分かります。

"use client";

import { useActionState } from "react";
import { subscribe } from "./actions";
// subscribe は { ok: boolean; text: string } を返す Server Action

type SubscribeState = { ok: boolean; text: string } | null;

export default function NewsletterForm() {
  const [state, formAction, isPending] = useActionState<SubscribeState, FormData>(
    subscribe,
    null
  );

  return (
    <form action={formAction}>
      <input name="email" placeholder="メールアドレス" />
      <button disabled={isPending}>
        {isPending ? "登録中..." : "登録する"}
      </button>
      {state && (
        <p style={{ color: state.ok ? "green" : "red" }}>{state.text}</p>
      )}
    </form>
  );
}