react-hook-form + zod : バリデーションの実装

Zodは、TypeScript向けのスキーマベースのバリデーションライブラリである。

入力データやAPIレスポンスなどのデータ構造の検証を、TypeScriptの型と一貫して行うことが可能であり、react + TypeScriptでの開発を行う上では、導入している企業やプロジェクトも多い印象だ。

本投稿では、その使用方法について、残しておく。

インストール

npmパッケージを使用する例。

npm install react-hook-form@latest zod @hookform/resolvers

コード例 ①

解説は、コメントとして残す。

import { useState } from 'react';
import { useForm, SubmitHandler } from 'react-hook-form'
import { z } from "zod"
import { zodResolver } from "@hookform/resolvers/zod"

const MyForm = () => {

  // パスワード
  const [password, setPassword] = useState<string>("")

  // zod スキーマ定義
  const schema = z.object({
    // バリデーションとして以下を設定 
    // 必須項目
    // ○○○@◽️◽️.▲▲▲の形式であるかの確認
    email: z.string().min(1, "メールアドレスを入力してください。").regex(/^[a-zA-Z0-9._]+@[a-zA-Z0-9]+\.[a-zA-Z]{2,}$/, "○○○@◽️◽️.▲▲▲の形式で入力してください。"),
    // バリデーションとして以下を設定
    // 必須項目
    // 半角英数字と記号(!?%_-+)が必ず含まれている かつ 8文字以上
    password: z.string().min(1, "パスワードを入力してください。").regex(/^(?=.*[a-zA-Z])(?=.*\d)(?=.*[!?%_\-+])[a-zA-Z\d!?%_\-+]{8,}$/, "半角英数字と記号(!?%_-+)が必ず含まれている かつ 8文字以上である必要があります。"),
    // バリデーションとして以下を設定
    // 必須項目
    // パスワードが一致しているか
    confirmPassword: z.string().refine((value) => value === password, {message: "パスワードが一致しません。"})
  })

  // 型の設定
  type FormValues = z.infer<typeof schema>

  // register、handleSubmit、errorsなどの関数やオブジェクトを取得
  // 型は、FormValueを設定
  const {
    register,
    handleSubmit,
    formState: {errors}
  } = useForm<FormValues>({
    // zodとreact-hook-formを紐づける
    resolver: zodResolver(schema)
  });

  // 送信ボタンが押下された場合のイベント
  // 型としてSubmitHandlerを設定
  const onSubmit: SubmitHandler<FormValues> = (data) => {
    console.log("submit", data)
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
       <div>
         <label>メールアドレス</label>
         <input 
          type="text"
          {...register("email")}
        />
        {
          errors.email && <p style={{ color: 'red', fontWeight: 'bold' }}>{ errors.email.message }</p>
        }
      </div>
      <div>
        <label>パスワード</label>
         {/* バリデーションとして以下を設定 */}
         {/* 必須項目 */}
         {/* 半角英数字と記号(!?%_-+)が必ず含まれている かつ 8文字以上 */}
         <input 
          type="password"
          {...register("password")}
          onChange={(e) => setPassword(e.target.value)}
        />
        {
          errors.password && <p style={{ color: 'red', fontWeight: 'bold'  }}>{ errors.password.message }</p>
        }
      </div>
      <div>
         <label>パスワード再入力</label>
         {/* バリデーションとして以下を設定 */}
         {/* 必須項目 */}
         {/* パスワードが一致しているか */}
         <input 
          type="password"
          {...register("confirmPassword")}
        />
        {
          errors.confirmPassword && <p style={{ color: 'red', fontWeight: 'bold'  }}>{ errors.confirmPassword.message }</p>
        }
      </div>
      <button type="submit">送信</button>
      <button type="reset">リセット</button>
    </form>
  )
}

export default MyForm;

動作例

以下、動作例。

コード例 ②

チェックボックス・ラジオボタン・セレクトボックスなどの場合。

import { useForm, SubmitHandler } from "react-hook-form"
import { z } from "zod"
import { zodResolver } from "@hookform/resolvers/zod"

const schema = z.object({
  //以下のバリデーションを設定
  //いずれかのチェックボックスを選択しているか
  //全て選択していないか(一つのみ選択可能)
  cb: z.array(z.string()).min(1, "少なくとも一つ選択してください。").max(1, "いずれか一方のみ選択可能です。"),
  rg: z.string(),
  //以下のバリデーションを設定
  //いずれかの要素が選択されているか(デフォルト値の選択してくださいもNG)
  sb: z.string().min(1, "選択肢を選択してください")
})

type FormValues = z.infer<typeof schema>

export default function MyForm() {
  
  const {
    register,
    handleSubmit,
    formState: { errors }
  } = useForm<FormValues>({
    defaultValues: {
      cb: ["value1"], // チェックボックスの初期値を設定
      rg: "value1" // ラジオボタンの初期値を設定
    },
    resolver: zodResolver(schema)
  });

  // 送信ボタンが押下された際のイベント
  const onSubmit: SubmitHandler<FormValues> = (data) => {
    console.log("submit", data)
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>チェックボックス</label>
        <div>
          <label>
            <input
              type='checkbox'
              value="value1"
              {...register("cb")}
            />
            value1
          </label>
          <label>
            <input
              type="checkbox"
              value="value2"
              {...register("cb")}
            />
            value2
          </label>
        </div>
        {errors.cb && (
          <p style={{ color: "red", fontWeight: "bold" }}>{errors.cb.message}</p>
        )}
      </div>
      <div>
        <label>ラジオボタン</label>
        <div>
          <label>
            <input
              type='radio'
              value='value1'
              {...register("rg")}
            />
            value1
          </label>
          <label>
            <input
              type='radio'
              value='value2'
              {...register("rg")}
            />
            value2
          </label>
        </div>
      </div>
      <div>
        <label>セレクトボックス</label>
        <select
          {...register("sb")}
        >
          <option value="">選択してください</option>
          <option value="value1">Value1</option>
          <option value="value2">Value2</option>
          <option value="value3">Value3</option>
        </select>
        {
          errors.sb && <p style={{ color: "red", fontWeight: "bold" }}>{ errors.sb.message }</p>
        }
      </div>
      <button type='submit'>送信</button>
      <button type="reset">リセット</button>
    </form>
  )
}

動作例

Follow me!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です