YASD-TECH
YASD TECH
# Next.js
# TypeScript

react hook form

投稿日:2024/6/8

更新日:2024/6/8

ttitleImage

前置き

仕事で割と複雑なformを取り扱うことになったのでその検証、備忘録です。

挙動のサンプル動画を入れたかったのですがmicrocmsのプランによってはできないみたいですね、、

なのでコードだけ置く形になります。

React Hook Formとは?

Reactでフォームを効率的に管理するためのライブラリです。最近のReactだとformは全部これ使ってるんじゃないかなと思います。

  • 軽量で高速
  • コードがシンプルになる
  • バリデーションが容易

    上記のようなメリットがあります。

React Hook Formのインストール

React Hook Formをインストールするには、以下のコマンドを使用します。

Terminal
$ npm install react-hook-form

基本的な使い方

ここでは、基本的なフォームの作成方法を紹介します。以下のコードは、名前、パスワードの入力フィールドを持つシンプルなフォームです。

page.tsx
"use client";
import { useForm, SubmitHandler } from "react-hook-form";

type Inputs = {
  name: string;
  password: string;
};

export default function Form() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<Inputs>();
  
  const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data);

  return (
    /* "handleSubmit" は "onSubmit" を呼び出す前に入力値を検証します  */
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* "register" 関数を呼び出してフックに入力を登録します */}
      
      <input defaultValue="test" {...register("name")} />
      {/* 必須や他の標準HTML検証ルールを含めて検証を行います */}
      
      <input {...register("password", { required: true })} />
      {/* 検証が失敗したときにエラーを返します */}
      {errors.password && <span>このフィールドは必須です</span>}

      <input type="submit" />
    </form>
  );
}

基本的な使い方です。

page.tsx
<input defaultValue="test" {...register("name")} />

...register("name")ここに入るのがパラメータ、defaultValue="test" ここに入るのがデフォルトの値です。

上記のフィールドから投げた値は、以下のようにデータを飛ばせます。

page.tsx
{ name: "test" }

エラーメッセージの表示

バリデーションは以下のように実施できます。

`errors`オブジェクトを使って、特定のフィールドのエラーを確認し、メッセージを表示します。

page.tsx
{errors.password && <span>このフィールドは必須です</span>}

setValue watch getValue useWatch

ラジオボタンで選ばれているボタンの値によって値を変更したいみたいなケースがあるかと思います。

選ばれている性別によって、色をリアルタイムで変更してみましょう。

今回は男が選ばれていればblue, 女が選ばれていればredを設定してみます。

watch, useWatch, onChangeを使ったスキーマがあるのでそれぞれ書いておきます。

watchを使う場合

page.tsx
'use client'
import { useEffect } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form'

type Inputs = {
  color: string
  gender: string
}

export default function Form() {
  const {
    register,
    handleSubmit,
    setValue,
    watch,
    formState: { errors },
  } = useForm<Inputs>()

  const gender = watch('gender')

  useEffect(() => {
    if (gender === '0') {
      setValue('color', 'blue')
    } else if (gender === '1') {
      setValue('color', 'red')
    }
  }, [gender])

  const onSubmit: SubmitHandler<Inputs> = (data) => {
    console.log(data)
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>
        <input
          type="radio"
          value={0}
          {...register('gender', { required: true })}
        />
        men
      </label>

      <label>
        <input
          type="radio"
          value={1}
          {...register('gender', { required: true })}
        />
        women
      </label>
      <br />
      <input
        {...register('color', { required: true })}
        defaultValue={'white'}
      />

      <input type="submit" />
    </form>
  )
}
page.tsx
const gender = watch("gender");

watchでregisterに設定しているgenderの部分の変更を監視します。

例えばラジオボタンでgernderが変更された時、その値がここに入ってきます。

page.tsx

  useEffect(() => {
    if (gender === "0") {
      setValue("color", "blue");
    } else if (gender === "1") {
      setValue("color", "red");
    }
  }, [gender]);

入ってきたgenderの値によってsetValueで更新をかけます。

数値で設定してもstringで飛ぶのでそこだけ注意です。

getValue 、onhangeを使う場合

page.tsx

'use client'
import { SubmitHandler, useForm } from 'react-hook-form'

type Inputs = {
  color: string
  gender: string
}

export default function Form() {
  const {
    register,
    handleSubmit,
    setValue,
    getValues,
    formState: { errors },
  } = useForm<Inputs>()

  const onSubmit: SubmitHandler<Inputs> = (data) => {
    console.log(data)
  }

  const handleGenderChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const gender = e.target.value
    if (gender === '0') {
      setValue('color', 'blue')
    } else if (gender === '1') {
      setValue('color', 'red')
    }
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>
        <input
          type="radio"
          value="0"
          {...register('gender', {
            required: true,
            onChange: handleGenderChange,
          })}
        />
        men
      </label>

      <label>
        <input
          type="radio"
          value="1"
          {...register('gender', {
            required: true,
            onChange: handleGenderChange,
          })}
        />
        women
      </label>

      <input
        {...register('color', { required: true })}
        defaultValue={'white'}
      />

      <input type="submit" />
    </form>
  )
}

useWatchを使う場合

page.tsx
'use client'
import { useEffect } from 'react'
import { SubmitHandler, useForm, useWatch } from 'react-hook-form'

type Inputs = {
  color: string
  gender: string
}

export default function Form() {
  const {
    register,
    handleSubmit,
    setValue,
    control,
    formState: { errors },
  } = useForm<Inputs>()

  const gender = useWatch({
    control,
    name: 'gender',
  })

  useEffect(() => {
    if (gender === '0') {
      setValue('color', 'blue')
    } else if (gender === '1') {
      setValue('color', 'red')
    }
  }, [gender, setValue])

  const onSubmit: SubmitHandler<Inputs> = (data) => {
    console.log(data)
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>
        <input
          type="radio"
          value={0}
          {...register('gender', { required: true })}
        />
        men
      </label>

      <label>
        <input
          type="radio"
          value={1}
          {...register('gender', { required: true })}
        />
        women
      </label>

      <br />

      <input {...register('color', { required: true })} defaultValue="white" />

      <input type="submit" />
    </form>
  )
}

formを動的に増減させたい

useFieldArrayを利用することで実装できます。

rails viewで言うcocoon的なやつです。

page.tsx
'use client'
import { SubmitHandler, useFieldArray, useForm } from 'react-hook-form'

type Inputs = {
    dynamicFields: { value: string }[]
}

export default function Form() {
    const {
        register,
        handleSubmit,
        control,
        formState: { errors },
    } = useForm<Inputs>({
        mode: 'onChange',
        defaultValues: {
            dynamicFields: [{ value: '' }],
        },
    })

    const { fields, append, remove } = useFieldArray({
        control,
        name: 'dynamicFields',
    })

    const onSubmit: SubmitHandler<Inputs> = (data) => {
        console.log(data)
    }

    return (
        <div>
            <form onSubmit={handleSubmit(onSubmit)}>
                {fields.map((field, index) => (
                    <div key={field.id}>
                        <input
                            {...register(`dynamicFields.${index}.value` as const, {
                                required: 'このフィールドは必須です',
                            })}
                        />
                        {errors.dynamicFields?.[index]?.value && (
                            <p className="error">
                                {errors.dynamicFields[index].value.message}
                            </p>
                        )}
                        <button type="button" onClick={() => remove(index)}>
                            削除
                        </button>
                    </div>
                ))}
                <button type="button" onClick={() => append({ value: '' })}>
                    追加
                </button>
                <input type="submit" />
            </form>
        </div>
    )
}

fields:現在のフィールド配列を保持するオブジェクト。

append:新しいフィールドを追加する関数。

remove: 既存のフィールドを削除する関数。

Index

  • 前置き
  • React Hook Formとは?
  • React Hook Formのインストール
  • 基本的な使い方
  • エラーメッセージの表示
  • setValue watch getValue useWatch
  • watchを使う場合
  • getValue 、onhangeを使う場合
  • useWatchを使う場合
  • formを動的に増減させたい