Nextjs14 Sever ActionとZodでバリデーションを実装する
更新日 2024-08-18
このブログ記事では、Next.js 14、Zod、Server Actionを使用して、サーバーサイドでフォームのバリデーションを行い、エラーメッセージをフォーム内に表示する方法を説明します。
はじめに
このブログ記事では、ZodとServer Actionを使用して、Next.js 14でサーバーサイドのバリデーションを実装する方法を説明します。Zodは、強力で使いやすいスキーマ定義ライブラリです。Server Actionは、Next.js 14で導入された新しい機能で、サーバーサイドでフォームデータを処理することができます。
バリデーションのセットアップ
Zodのインストール
以下のコマンドでZodをインストールします。
npm i zod
スキーマの定義
続いてバリデーションを定義します。
今回はコンタクトフォームのバリデーションを行います。
下記はお名前1文字以下、メールアドレスのフォーマットミス、メッセージ1文字以下の場合、各{message:}で定義した文字列でバリデーションエラーを返します。
import { z } from 'zod';
export const validate = z.object({
name: z.string().min(1, { message: 'Name is required' }),
email: z.string().email({ message: 'Invalid email' }),
message: z.string().min(1, { message: 'Message is required' }),
});
ServerActionの実装
import { validate } from './validate';
export type Errors = {
erros?: {
name?: string[];
email?: string[];
message?: string[];
};
message?: null | string;
};
import { validate } from './validate';
export type Errors = {
errors?: {
name?: string[];
email?: string[];
message?: string[];
};
message?: null | string;
};
export async function ServerAction(prevState: any, formData: FormData) {
const validateResult = validate.safeParse({
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message'),
});
await new Promise((resolve) => setTimeout(resolve, 2000));
if (!validateResult.success) {
const errors = {
errors: validateResult.error.flatten().fieldErrors,
message: 'Validation error',
};
return errors;
}
// これ以下にサーバーとの通信処理を書く
}
フォームの実装
'use client';
import { ServerAction } from './lib/ServerAction';
import { Errors } from './lib/ServerAction';
import { useRef } from 'react';
import { useFormState, useFormStatus } from 'react-dom';
import { useRouter } from 'next/navigation';
import Buton from '@/components/button';
export default function Page() {
const initialState: Errors = {
errors: {
name: [],
email: [],
message: [],
},
message: null,
};
const formRef = useRef<HTMLFormElement>(null);
const [state, dispatch] = useFormState(ServerAction, initialState);
const router = useRouter();
const { pending } = useFormStatus();
return (
<div className='mx-64 my-10 rounded-lg bg-white '>
<form
action={async (payload: FormData) => {
dispatch(payload);
if (state?.errors === null) {
router.push('/success');
}
}}
ref={formRef}
className='p-8 text-xl font-medium space-y-4'
>
{state?.message && (
<p className='text-red-500 text-2xl font-bold'>{state.message}</p>
)}
<label className=''>Name</label>
<input
name='name'
className='w-full p-3 rounded-xl border border-black'
/>
{state?.errors?.name && (
<p className='text-red-500 text-xs'>{state.errors?.name}</p>
)}
<label>Email</label>
<input
name='email'
className='w-full p-3 rounded-xl border border-black'
/>
{state?.errors?.email && (
<p className='text-red-500 text-xs'>{state.errors?.email}</p>
)}
<label>Message</label>
<textarea
name='message'
className='w-full rounded-xl border border-black'
rows={5}
/>
{state?.errors?.message && (
<p className='text-red-500 text-xs mt-1'>{state.errors?.message}</p>
)}
<Buton />
</form>
</div>
);
}
ついでにuseFormStatusを使用してペンディングも実装しました。
ペンディングはボタンをコンポーネント化しないとエラーが出でしまいます。原因はわかりません。
以下ボタンコンポーネントです。
import { useFormStatus } from 'react-dom';
const Buton: React.FC = () => {
const { pending } = useFormStatus();
return (
<button
type='submit'
className='px-8 py-3 bg-blue-500 text-white rounded-md'
disabled={pending}
>
{pending ? 'Sending' : ' Send '}
</button>
);
};
export default Buton;
まとめ
Nextjs14でZodとServer Actionを使用してサーバーサイドでバリデーションを行いエラーメッセージをフォーム内に表示させました。
今回はuseFormStateで実装しましたが、Nextjs15RCからuseServerAction が使用可能になりました。Nextjs15がstableとなった時またuseActionStateを紹介したいと思います。
また、formのactionをpayloadを引数にすることでサーバーでの処理が終了した後クライアントでの処理を記述することができます。この情報はネットで見つけることができなかったので共有させていただきました。どなたかの参考になれば幸いです。
それではまたお会いしましょう。