😺

フォームアクションをuseActionStateで管理する

2025/02/10に公開

はじめに

Next.jsのフォームアクションについて状態管理する方法はいくつかあります。
今回はuseActionStateを使ってフォームアクションを管理する方法を紹介します。

useActionStateとは

useActionStateは、React v19で導入された新しいフックです。
useActionStateは、useStateと同じように状態を管理するためのフックですが、フォームアクションなどの非同期処理を簡単に管理できるようになっています。

なぜuseActionStateを使うのか

useStateでもフォームアクションを管理可能です。しかし処理が複雑になってしまいます。例えば非同期の進行状態やエラーなどを管理するために多くのステートを用いる必要がありますね。

以下のように、useStateを使ってフォームアクションを管理すると、コードが複雑になってしまいますね。

useStateを使ったフォームアクションの例。
const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle");
const [data, setData] = useState<any>(null);
const [error, setError] = useState<string | null>(null);

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();

  setStatus("loading");

  try {
    const title = e.target.title.value;
    const body = e.target.body.value;
    
    const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
      method: "POST",
      body: JSON.stringify({ title, body }),
      headers: {
        "Content-Type": "application/json",
      },
    });

    if (!response.ok) {
      const error = await response.text();
      setError(error);
      setStatus("error");
      return;
    }

    const post = await response.json();
    setData(post);
    setStatus("success");
  } catch (error) {
    setError(error.message);
    setStatus("error");
  }
};

return (
  <form onSubmit={handleSubmit}>
    {status === "loading" && <p>Loading...</p>}
    {status === "success" && <p>{JSON.stringify(data)}</p>}
    {status === "error" && <p>{error}</p>}
    <input type="text" name="title" />
    <textarea name="body" />
    <button type="submit">Submit</button>
  </form>
);

useActionStateではそのような問題は解消されます。非同期処理の進行状態やエラーを簡単に管理できるため、コードがシンプルになります。

使い方

早速、useActionStateを使ってフォームアクションを管理してみましょう。

ここで使用しているパッケージは以下の通りです。

{
  "@chakra-ui/react": "^3.7.0",
  "@emotion/react": "^11.14.0",
  "next": "15.1.6",
  "next-themes": "^0.4.4",
  "react": "^19.0.0",
  "react-dom": "^19.0.0",
  "react-icons": "^5.4.0"
}

またAPIとして、JSONPlaceholderを使用しています。

サンプルコード

簡単なフォームを作成しています。

  1. タイトルとコンテンツを入力し、送信ボタンを押します。
  2. フォームアクションにより、APIに入力したデータを作成するリクエストが送信されます。
  3. 内容に応じたレスポンスが返され、アラートで表示されます。

ここでのポイントは、useActionStateを使ってフォームアクションを管理している点です。 useStateを扱わなくとも、非同期処理の進行状態やエラーを簡単に管理できることがわかります。
useEffectに依存配列としてstateを指定しているため、stateが変更されるたびにアラートが表示されます。

src/app/page.tsx
'use client';

import { useActionState, useEffect } from "react";
import { Button, Input, Container, Stack, Fieldset, Textarea } from "@chakra-ui/react"
import { Field } from "@/components/ui/field"
import { actionCreatePost } from "@/app/action"

export default function Home() {
  const [state, formAction] = useActionState(actionCreatePost, null);

  useEffect(() => {
    if (!state) {
      return;
    }

    if (state.status === "success") {
      alert(JSON.stringify(state.data));
    } else if (state.status === "error") {
      alert(state.error);
    }
  }, [state]);

  return (
    <Container maxW="sm" py={16}>
      <form action={formAction}>
        <Fieldset.Root size="lg" maxW="md">
          <Stack>
            <Fieldset.Legend>Post</Fieldset.Legend>
            <Fieldset.HelperText>
              Please fill out the form below to create a new post.
            </Fieldset.HelperText>
          </Stack>

          <Fieldset.Content>
            <Field label="タイトル" required>
              <Input name="title" />
            </Field>

            <Field label="コンテンツ" required>
              <Textarea name="body" />
            </Field>
          </Fieldset.Content>

          <Button type="submit" alignSelf="flex-start">
            送信
          </Button>
        </Fieldset.Root>
      </form>
    </Container>
  );
}

アクション関数

先ほどのフォームに指定されていた、フォームアクション関数を作成しています。

ここはAPIリクエストを送信するための関数です。フォームデータを受け取り、APIにリクエストを送信します。サーバーアクションなので、'use server'を指定しています。

src/app/action.ts
'use server';

type Result = {
  title: string
  body: string
  id: number
}

export const actionCreatePost = async (prevState, formData: FormData) => {
  const title = formData.get("title") as string;
  const body = formData.get("body") as string;

  const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
    method: "POST",
    body: JSON.stringify({ title, body }),
    headers: {
      "Content-Type": "application/json",
    },
  });

  if (!response.ok) {
    const error = await response.text();

    return {
      status: 'error',
      data: null,
      error,
    };
  }

  const post = await response.json() as Result;

  return {
    status: 'success',
    data: post,
    error: null,
  };
}

まとめ

useActionStateを使うことで、フォームアクションを簡単に管理できることがわかりました。
useStateを使うよりも、非同期処理の進行状態やエラーを簡単に管理できるため、コードがシンプルになります。

今回はフォームアクションを例に挙げましたが、フォームに限らず他の非同期処理にも応用できます。ぜひ、useActionStateを使ってみてください。

参考

Discussion