フォームアクションをuseActionStateで管理する
はじめに
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を使用しています。
サンプルコード
簡単なフォームを作成しています。
- タイトルとコンテンツを入力し、送信ボタンを押します。
- フォームアクションにより、APIに入力したデータを作成するリクエストが送信されます。
- 内容に応じたレスポンスが返され、アラートで表示されます。
ここでのポイントは、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