2024.4.28
Zodだけで足りる場合のバリデーション実装例
概要
React Hook Formなどのライブラリを使うまでもない場合のフォームのバリデーションをZodのみで実装します。
HTML要素を基本的な構造で作ればformタグのonChangeイベントでformタグ内の入力DOM要素のonChangeイベントは捕捉できるので、入力されるごとにバリデーション結果を更新することもできます。
onChangeとonSubmit時にバリデーションを行う例を実装します。
また、実装例を作っただけなので細かいことは適当ですがリポジトリは以下です。
GitHub - io-kobayashiii/test-validation-library-sample
完成形
まずはブラウザ上での完成形を確認します。
エラーなし
エラーあり(onChange)
エラーあり(onSubmit)
実装内容
上記の完成形のコンポーネントは以下のリンクのファイルです。
GitHub - ZodAndDefaultDomElementsWithRealtimeValidation.tsx
Zodスキーマの定義
tsx_____formSchema_____const formSchema = z.object({
username: z.string().min(1, 'Username cannot be empty.'),
email: z.string().email('Email is invalid.'),
});
状態管理
このサンプルでは、Reactでコントロールするのは各入力DOM要素のエラー状態のみです。
値は各入力DOM要素が保持しているので管理しません。
onChangeハンドラ
formタグのonChangeハンドラで、バブリングでReact.ChangeEventを捕捉し、formタグ内の変更に応じてバリデーションを実行します。fieldSchema.parse
に target.value
を渡すため、input、textarea、selectタグであることを保証します。
また target.name
をkeyとして formError
を更新することで、このイベントで捕捉した入力DOM要素に対するエラー状態だけ更新できます。
tsx_____handleFormChange_____ const handleFormChange = (event: React.ChangeEvent<HTMLFormElement>) => {
const { target } = event;
const key = target.name as keyof typeof formSchema.shape;
const fieldSchema = formSchema.shape[key];
if (
target instanceof HTMLInputElement ||
target instanceof HTMLTextAreaElement ||
target instanceof HTMLSelectElement
) {
try {
fieldSchema.parse(target.value);
setFormError((state) => ({ ...state, [key]: '' }));
} catch (error) {
if (error instanceof ZodError) {
const [{ message }] = error.issues;
setFormError((state) => ({ ...state, [key]: message }));
}
}
}
};
onSubmitハンドラ
tsx_____handleSubmit_____ const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const target = event.target as HTMLFormElement;
const formData = new FormData(target);
try {
formSchema.parse(Object.fromEntries(formData.entries()));
setFormError({ username: '', email: '' });
// {
// // バリデーションチェックをパスした場合の処理...
// }
} catch (error) {
if (error instanceof ZodError) {
const newErrors: { [key: string]: string } = {};
error.issues.forEach((issue) => {
newErrors[issue.path[0]] = issue.message;
});
setFormError(newErrors as z.infer<typeof formSchema>);
}
}
};
エラーメッセージ
エラーメッセージを表示する要素は好きなところに配置します。
tsx_____error message_____ {formError.username && (
<p className="mt-1 text-red-400 leading-none">
{formError.username}
</p>
)}
まとめ
シンプルにZodとDOMで定義されているイベントでバリデーションをハンドリングできました。