React Hook FormとZodの基本的な使い方

## はじめに
かんたんなコードを交えつつ、React Hook FormとZodの基本的な使い方をまとめます。

## React Hook Formとは
React Hook FormとはReact用のフォーム作成ライブラリです。
フォームの入力値の取得やバリデーション機能などを備えています。
`useForm`というカスタムフックが用意されており、フォームの管理ができます。

### useForm
`useForm`はフォームを管理するためのカスタムフックです。
オプショナルの引数として一つのオブジェクトを渡すことができます。
以下のプロパティを設定できます。（一部抜粋）

| プロパティ | 概要 |
|:-----------|:------------|
| mode | 初回バリデーションのタイミングを設定 |
| reValidateMode | 初回バリデーション実行後のバリデーションのタイミングの設定 |
| defaultValues| フォームの初期値を設定 |
| resolver | YupやZodなどの外部バリデーションライブラリを利用する場合に設定 |

そして以下のメソッドが返されます。（一部抜粋）
これらのメソッドを使ってフォームを作成します。

| メソッド | 概要 |
|:-----------|:------------|
| register | inputなどに入力された値を参照するために使う |
| handleSubmit | バリデーションが成功した際、フォームのデータを受け取ることができる |
| trigger| バリデーションのタイミングを手動で設定するために使う |

## Zodとは
Zodはバリデーションライブラリです。（React Hook Form専用ではない）
スキーマを定義し、バリデーションの設定を行うことができます。
また定義したスキーマからTypeScriptの型を生成することができます。

## 使用例
![スクリーンショット 2023-05-03 11.31.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633215/fecec88a-3ffd-cfdc-2eae-04ee1134b4dc.png)

続いて、このようなフォーム画面をReact Hook FormとZodでどのように実装できるか見ていきます。
仕様や環境は以下の通りです。

- 仕様
    - 「名前」は1文字以上、必須
    - 「年齢」は任意
    - 「パスワード」は英大文字or英小文字or数字を使って8文字以上、必須
    - 「送信」ボタンクリック時に、初回バリデーションのチェック
    - バリデーションが成功したら入力値を取得できる
- 環境
    - Next.js 13.3.4
    - react-hook-form 7.43.9
    - zod 3.21.4
    - chakra-ui 2.6.0

```tsx:use-basic-form-top.tsx
import { useForm } from "react-hook-form";
import {
  basicFormSchema,
  BasicFormSchemaType,
} from "@/features/basic-form/schema";
import { zodResolver } from "@hookform/resolvers/zod";

export const useBasicFormTop = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<BasicFormSchemaType>({
    resolver: zodResolver(basicFormSchema),
  });

  const onSubmit = (data: BasicFormSchemaType) => {
    console.log(data);
  };

  return {
    register,
    onSubmit: handleSubmit(onSubmit),
    errors,
  };
};
```

- `useForm`について
    - `register`,`handleSubmit`,`errors`を受け取っています
    - Zodを使うためには、引数に`{resolver: zodResolver(スキーマ名)}`を設定する必要があります
    - `BasicFormSchemaType`はスキーマから生成した型です
- `onSubmit`について
    - `data`は`handleSubmit`を通じて得られる、バリデーション成功後のフォームのデータです

```tsx:basic-form-top.tsx
import {
  Container,
  Heading,
  FormControl,
  FormLabel,
  FormErrorMessage,
  Input,
  Button,
} from "@chakra-ui/react";
import { useBasicFormTop } from "@/features/basic-form/hooks";

export const BasicFormTop = () => {
  const { register, onSubmit, errors } = useBasicFormTop();
  return (
    <Container mt={12}>
      <Heading as={"h2"} mb={8}>
        基本的なフォーム
      </Heading>
      <form onSubmit={onSubmit}>
        <FormControl isInvalid={!!errors.name}>
          <FormLabel htmlFor={"name"}>名前</FormLabel>
          <Input type={"text"} id={"name"} {...register("name")} />
          <FormErrorMessage>
            {errors.name && errors.name.message}
          </FormErrorMessage>
        </FormControl>

        <FormControl mt={8}>
          <FormLabel htmlFor={"age"}>年齢</FormLabel>
          <Input type={"text"} id={"age"} {...register("age")} />
        </FormControl>

        <FormControl isInvalid={!!errors.password} mt={8}>
          <FormLabel htmlFor={"password"}>パスワード</FormLabel>
          <Input type={"password"} id={"password"} {...register("password")} />
          <FormErrorMessage>
            {errors.password && errors.password.message}
          </FormErrorMessage>
        </FormControl>

        <Button type={"submit"} mt={8}>
          送信
        </Button>
      </form>
    </Container>
  );
};
```

- `register`の第1引数には名前を設定します
- フォームのonSubmit時に`handleSubmit(onSubmit)`を実行しています
- バリデーションに失敗した場合、`errors`にエラーに関するオブジェクトが格納されます

```tsx:index.ts
import { z } from "zod";

export const basicFormSchema = z.object({
  name: z.string().min(1, { message: "名前を入力してください" }),
  age: z.string().nullable(),
  password: z
    .string()
    .min(8, { message: "8桁以上のパスワードを入力してください" })
    .regex(/^[a-zA-Z0-9]+$/, {
      message: "英大文字、英小文字、数字で入力してください",
    }),
});

export type BasicFormSchemaType = z.infer<typeof basicFormSchema>;
```

- `z.object({})`でスキーマを定義します
- 各プロパティは`register`の第1引数で設定した名前と対応しています
- `z.string()`は文字列であること、`z.min(1)`は1文字以上であること、を定義できます
- `z.string().min(1)`といったように、関数をつなぐことでバリデーションルールを設定できます
- `z.infer<typeof スキーマ名>`とすることで、スキーマから型を生成できます

## 参考記事
https://react-hook-form.com/api/useform/

https://reffect.co.jp/react/react-hook-form

https://zenn.dev/kaz_z/articles/how-to-use-zod

https://zenn.dev/uzimaru0000/articles/react-hook-form-with-zod

https://zenn.dev/uttk/articles/bd264fa884e026

## 採用のお知らせ
株式会社Relicでは、エンジニア・デザイナーを積極的に採用中です。
またRelicでは、地方拠点がありますので、U・Iターンも大歓迎です！🙌
少しでもご興味がある方は、Relic採用サイトからエントリーください！

https://relic.co.jp/recruit?utm_source=qiita&utm_medium=article&utm_campaign=qiita_article

https://relic.co.jp/categories/job_4?utm_source=qiita&utm_medium=article&utm_campaign=qiita_article