For

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.parsetarget.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で定義されているイベントでバリデーションをハンドリングできました。