- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Collapsible
- Combobox
- Command
- Context Menu
- Data Table
- Date Picker
- Dialog
- Direction
- Drawer
- Dropdown Menu
- Empty
- Field
- Hover Card
- Input
- Input Group
- Input OTP
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Message Scroller
- Native Select
- Navigation Menu
- Pagination
- Popover
- Progress
- Questionnaire
- Radio Group
- Resizable
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Toggle Group
- Tooltip
- Typography
このガイドでは、useActionState とサーバー アクションを使用して Next.js でフォームを構築する方法を見ていきます。フォームの構築、検証、保留状態、アクセシビリティなどについて説明します。
デモ#
単純なテキスト入力とテキストエリアを備えた次のフォームを構築します。送信時に、サーバー アクションを使用してフォーム データを検証し、フォームの状態を更新します。
コンポーネント form-next-demo が見つかりません。
注: このページの例では、サーバー アクションでスキーマ検証とフォーム エラーがどのように機能するかを示すため、ブラウザー検証を意図的に無効にしています。
アプローチ#
このフォームは、Next.js と React の組み込み機能を利用してフォームを処理します。 <Field /> コンポーネントを使用してフォームを構築します。これにより、マークアップとスタイルを完全に柔軟に行うことができます。
- ナビゲーションと段階的な機能強化に Next.js の
<Form />コンポーネントを使います。 - アクセシブルなフォームの構築に
<Field />コンポーネントを使います。 useActionStateでフォームの状態とエラーを管理します。- 保留中のプロパティを使用してロード状態を処理します。
- フォーム送信の処理にサーバー アクションを使います。
- Zod を使用したサーバー側の検証。
構成#
<Field /> コンポーネントを使用したフォームの基本的な例を次に示します。
<Form action={formAction}>
<FieldGroup>
<Field data-invalid={!!formState.errors?.title?.length}>
<FieldLabel htmlFor="title">Bug Title</FieldLabel>
<Input
id="title"
name="title"
defaultValue={formState.values.title}
disabled={pending}
aria-invalid={!!formState.errors?.title?.length}
placeholder="Login button not working on mobile"
autoComplete="off"
/>
<FieldDescription>
Provide a concise title for your bug report.
</FieldDescription>
{formState.errors?.title && (
<FieldError>{formState.errors.title[0]}</FieldError>
)}
</Field>
</FieldGroup>
<Button type="submit">Submit</Button>
</Form>使い方#
フォーム スキーマを作成する#
まず、schema.ts ファイル内の Zod スキーマを使用してフォームの形状を定義します。Zod は、標準スキーマでサポートされている他のスキーマ検証ライブラリに置き換えられます。
注: この例では、スキーマ検証に zod v3 を使用していますが、
Zod は他のスキーマ検証ライブラリに置き換えられます。スキーマ ライブラリが標準スキーマ仕様に準拠していることを確認してください。
import { z } from "zod"
export const formSchema = z.object({
title: z
.string()
.min(5, "Bug title must be at least 5 characters.")
.max(32, "Bug title must be at most 32 characters."),
description: z
.string()
.min(20, "Description must be at least 20 characters.")
.max(100, "Description must be at most 100 characters."),
})フォーム状態の型を定義する#
次に、値、エラー、成功状態を含むフォーム状態の型を作成します。この型はクライアントとサーバーのフォーム状態に使用します。
import { z } from "zod"
export type FormState = {
values?: z.infer<typeof formSchema>
errors: null | Partial<Record<keyof z.infer<typeof formSchema>, string[]>>
success: boolean
}重要: スキーマと FormState タイプを別のファイルで定義して、それらをクライアント コンポーネントとサーバー コンポーネントの両方にインポートできるようにします。
サーバー アクションを作成する#
サーバー アクションはサーバー上で実行され、クライアントから呼び出せる関数です。フォーム データの検証とフォーム状態の更新に使います。
注: エラーの場合は values を返します。これは、ユーザーが送信した値をフォームの状態に維持したいためです。成功した場合は、空の値を返してフォームをリセットします。
フォームの構築#
<Field /> コンポーネントを使用してフォームを構築できるようになりました。 useActionState フックを使用して、フォームの状態、サーバー アクション、および保留状態を管理します。
完了#
これで、クライアント側とサーバー側の検証を備えた、完全にアクセシブルなフォームが完成しました。
フォームを送信すると、formAction 関数がサーバー上で呼び出されます。サーバー アクションはフォーム データを検証し、フォームの状態を更新します。
フォーム データが無効な場合、サーバー アクションはエラーをクライアントに返します。有効な場合は成功状態を返し、フォーム状態を更新します。
保留中の状態#
useActionState の pending プロパティを使用して、読み込みインジケーターを表示し、フォーム入力を無効にします。
"use client"
import * as React from "react"
import Form from "next/form"
import { Spinner } from "@/components/ui/spinner"
import { bugReportFormAction } from "./actions"
export function BugReportForm() {
const [formState, formAction, pending] = React.useActionState(
bugReportFormAction,
{
errors: null,
success: false,
}
)
return (
<Form action={formAction}>
<FieldGroup>
<Field data-disabled={pending}>
<FieldLabel htmlFor="name">Name</FieldLabel>
<Input id="name" name="name" disabled={pending} />
</Field>
<Field>
<Button type="submit" disabled={pending}>
{pending && <Spinner />} Submit
</Button>
</Field>
</FieldGroup>
</Form>
)
}無効な状態#
送信ボタン#
送信ボタンを無効にするには、ボタンの disabled プロップで pending プロップを使用します。
<Button type="submit" disabled={pending}>
{pending && <Spinner />} Submit
</Button>フィールド#
<Field /> コンポーネントに無効な状態とスタイルを適用するには、<Field /> コンポーネントで data-disabled プロパティを使用します。
<Field data-disabled={pending}>
<FieldLabel htmlFor="name">Name</FieldLabel>
<Input id="name" name="name" disabled={pending} />
</Field>バリデーション#
サーバー側検証#
サーバー アクションのスキーマで safeParse() を使用して、フォーム データを検証します。
"use server"
export async function bugReportFormAction(
_prevState: FormState,
formData: FormData
) {
const values = {
title: formData.get("title") as string,
description: formData.get("description") as string,
}
const result = formSchema.safeParse(values)
if (!result.success) {
return {
values,
success: false,
errors: result.error.flatten().fieldErrors,
}
}
return {
errors: null,
success: true,
}
}ビジネス ロジックの検証#
サーバー アクションにカスタム検証ロジックを追加できます。
検証エラーが発生した場合は必ず値を返します。これは、フォームの状態がユーザーの入力を確実に維持するためです。
"use server"
export async function bugReportFormAction(
_prevState: FormState,
formData: FormData
) {
const values = {
title: formData.get("title") as string,
description: formData.get("description") as string,
}
const result = formSchema.safeParse(values)
if (!result.success) {
return {
values,
success: false,
errors: result.error.flatten().fieldErrors,
}
}
// Check if email already exists in database.
const existingUser = await db.user.findUnique({
where: { email: result.data.email },
})
if (existingUser) {
return {
values,
success: false,
errors: {
email: ["This email is already registered"],
},
}
}
return {
errors: null,
success: true,
}
}エラーの表示#
<FieldError /> を使用してフィールドの横にエラーを表示します。 data-invalid プロパティを <Field /> コンポーネントに追加し、aria-invalid プロパティを入力に追加してください。
<Field data-invalid={!!formState.errors?.email?.length}>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input
id="email"
name="email"
type="email"
aria-invalid={!!formState.errors?.email?.length}
/>
{formState.errors?.email && (
<FieldError>{formState.errors.email[0]}</FieldError>
)}
</Field>フォームをリセット#
サーバー アクションを使用してフォームを送信すると、React はフォームの状態を自動的に初期値にリセットします。
成功時にリセットする#
成功時にフォームをリセットするには、サーバー アクションから values を省略すると、React がフォームの状態を自動的に初期値にリセットします。これは標準的な React の動作です。
export async function demoFormAction(
_prevState: FormState,
formData: FormData
) {
const values = {
title: formData.get("title") as string,
description: formData.get("description") as string,
}
// Validation.
if (!result.success) {
return {
values,
success: false,
errors: result.error.flatten().fieldErrors,
}
}
// Business logic.
callYourDatabaseOrAPI(values)
// Omit the values on success to reset the form state.
return {
errors: null,
success: true,
}
}検証エラー時に保持する#
失敗時にフォームがリセットされないようにするには、サーバー アクションで値を返すことができます。これは、フォームの状態がユーザーの入力を確実に維持するためです。
export async function demoFormAction(
_prevState: FormState,
formData: FormData
) {
const values = {
title: formData.get("title") as string,
description: formData.get("description") as string,
}
// Validation.
if (!result.success) {
return {
// Return the values on validation errors.
values,
success: false,
errors: result.error.flatten().fieldErrors,
}
}
}複雑なフォーム#
複数のフィールドと検証を含む、より複雑なフォームの例です。
コンポーネント form-next-complex が見つかりません。