296
198

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

react-hook-formの使い方まとめ

Last updated at Posted at 2021-09-29

久しぶりの投稿になります。今後は月1ぐらいで記事投稿できるようにに頑張ります!以前react-hook-formについての記事を書いたのですが、バージョン7になって大きく変わったので改めて書いてみました。バージョン7で大きく変わったので、バージョン7より前を使っている方も多いと思い、以前書いた記事も残しす形にしました。

バージョン6はこちら↓
react-hook-formの使い方を解説 v6.13.0 useController追加!

参考:公式ドキュメント

react-hook-formとは

inputとかのformに関係するデータを使う際に、useStateを使うときよりもレンダリング回数を減らせたり、バリデーションも簡単に実装できてとても便利です!

インストール

## yarnの場合
yarn add react-hook-form

## npmの場合
npm install react-hook-form

以上!TypeScriptの型定義も含まれてます。

↓↓↓簡単な例

import { useForm, SubmitHandler } from 'react-hook-form';
import { ErrorMessage } from '@hookform/error-message';

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

export const Demo = () => {
  const {
    register,
    handleSubmit,
    reset,
    formState: { errors },
  } = useForm<Inputs>();

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

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name')} />
      <input
        {...register('email', {
          required: true,
          maxLength: 60,
          pattern: {
            value:
              /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
            message: 'メールアドレスの形式が不正です',
          },
        })}
      />
      <ErrorMessage errors={errors} name="email" />
      <button type="submit">Submit</button>
    </form>
  );
};

registerの記述が大きく変わり、以前name属性で書いていた部分を第一引数で書くようになり、入力補完も効くようになりました!registerを使うときはref属性に書くのではなく、スプレッド構文を使うようになりました。
他にもerrorsがformStateの中に入ったりしています。

↓この例だと入力した値はこんな風に出力されます。それぞれの機能を解説して行きます。

{name: 入力した値,email: 入力した値}

useForm

react-hook-formからインポートするものはいくつかあるが、基本的にはほとんどuseFormから取得することが多い。

const { register, reset, handleSubmit } = useForm({
  mode: onSubmit,
  defaultValues: {name: "aaa", email: test@test.com}
})

引数にオブジェクトで色んな設定みたいなのができます

名前 説明
mode 初回のバリデーションの実行タイミング
(初期値:onSubmit)
onChange or onBlur or onSubmit or onTouched or all
reValidateMode 初回のバリデーション実行後、次のバリデーション実行タイミング
(初期値: onChange)
onChange or onBlur or onSubmit
defaultValues 初期値をオブジェクトで入力します
(resetでdefaultValuesの値に戻る)
{name属性の値: 初期値,...}
resolver 他のvalidationライブラリを使う時に使用
context 基本的には他のvalidationライブラリを使う時に使用?
criteriaMode バリデーション時に最初に発生したエラーもしくは全てのエラーを収集
(初期値: firstError)
firstError or all
shouldUnregister 入力要素がアンマウントされたら、値の参照を解除、falseなら入力された値を保持
(初期値: false)
boolean
shouldFocusError フォームが送信されて、エラーが含まれているときにエラーのある最初のフィールドにフォーカスする
(初期値: true)
boolean
delayError エラー表示をミリ秒単位で遅らせる number
shouldUseNativeValidation ブラウザの元々のバリデーションを有効にする
(初期値: false)
boolean

register

inputなどに入力された値を参照するために使う。name属性を設定する必要があり、registerはrefに入れる。UIライブラリを使う場合はcontrol参照。一番使う。

↓第一引数の付け方によって、dataの形が変わる.バージョン7以前と指定方法が変わりました。tsの場合は型も変える必要あり

register("firstName") →  {firstName: 'value'}
register("name.firstName") →  {name: { firstName: 'value' }}
register("name.firstName.0") →  {name: { firstName: [ 'value' ] }}

引数にオブジェクトで色んなバリデーションが設定できる.ref属性とname属性を使わず、第一引数に元々のnameを指定するようになった。

名前 説明
required 必須項目にするか true or false or エラーメッセージ
maxLength 最大文字数 {value: number, message: エラーメッセージ}
minLength 最小文字数 {value: number, message: エラーメッセージ}
max 最大データ量 {value: number, message: エラーメッセージ}
min 最小データ量 {value: number, message: エラーメッセージ}
pattern 文字の形式 {value: 正規表現, message: エラーメッセージ}
validate カスタム {任意の名前: value => booleanを返す式 or 関数 || エラーメッセージ}
valueAsNumber 数値を返す、問題があればNaNを返す boolean
valueAsDate 日付を返す、問題があればnullを返す boolean
setValueAs 関数に値を通す 例)setValueAs:(value) => trim(value)
disabled 入力された値が無効になる
(初期値: false)
boolean
onChange onChangeイベント時に実行される関数
onBlur onBlurイベント時に実行される関数
value 入力の値 string, numberなど
shouldUnregister アンマウント後に参照が解除され、初期値も削除される
(初期値: false)
boolean
    <input
        {...register('email', {
          required: true,
          maxLength: 60,
          pattern: {
            value:
              /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
            message: 'メールアドレスの形式が不正です',
          },
          validate: {
            lessThanTen: (value) =>
              parseInt(value, 60) < 60 || '60文字以内で入力して下さい',
          },
        })}
      />

unregister

引数に入れたnameはregisterで参照しなくなる。配列を入れて、複数も可能。unregister(['name','email'])

<button onClick={() => unregister("email")}>email_unregister</button>

watch

第一引数入れたnameの値を監視。第二引数は初期値を設定可能。unregisterと同様に配列に入れて複数も可能。何も入れないと、全てのnameの値を取得。レンダリング回数は増える。また、元々はオブジェクトで返されていたが、バージョン7で配列として返されるようになった。

const watchName = watch("name", false);
const watchAllFields = watch();
const watchFields = watch(["name", "email"]);

handleSubmit

ラップした関数にformのdataをオブジェクトの形で渡してくれる。on_submitの引数にdataがあるのはそのため。
event.preventDefalut()も必要なし。

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

///

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

return (
    <form onSubmit={handleSubmit(onSubmit)}>
...

###reset

formの中の状態を初期化する関数で指定した値を初期化し、指定なしなら全ての値。
第二引数でリセット時の設定が可能。

const onClick =()=>{
  reset(values);
}

setError

第一引数に指定したnameにエラーをセットする関数。第二引数にtype(requiredやminLengthなど)とエラーメッセージをオブジェクトとしていれる。
typeを変えるとどう変わるかが、よく分からないので分かる方いたら教えて下さい!

<button onClick={() => setError("email", { type: "required", message: "" })}>

###clearErrors
引数に入れたnameのerrorを消す関数。こちらも配列で複数指定できる。

<button onClick={() => clearErrors("name")}>

getValues

引数に入れたnameの現在の値を取得。こちらも配列に入れて複数、空なら全てのnameの値を取得。watchとの違いは常に監視せず、再レンダリングもされない。ボタンを押して時に取得するとかならこっちの方がパフォーマンス的に良さそう。

const values = getValues();
const singleValue = getValues("name");
const multipleValues = getValues(["name", "email"]);

setValue

第一引数に入れたnameに第二引数に入れた値をセットする関数。その際にshouldValidateでバリデーションを実行するかどうか、shouldDirtyで変更後に変更の有無の判定を行うかどうか

<button onClick={()=> setValue('name', 'value', { shouldValidate: true, shouldDirty: true })}>Set</button>

setFocus

第一引数に入れたnameに第二引数に入れた値をセットする関数。その際にshouldValidateでバリデーションを実行するかどうか、shouldDirtyで変更後に変更の有無の判定を行うかどうか

<button onClick={()=> setFocya('name')}>Focus</button>

trigger

引数に入れたnameのバリデーションを実行する関数。こちらも配列で複数、空なら全体。元々はbooleanを返していたが、バージョン7で何も返さなくなった。

<button type="button" onClick={() => { trigger("lastName") }}>Trigger</button>

control

Controllerのnameの値を参照するのに使う。バリデーションはできない。useWatchuseFieldArrayにも使う。

<Controller
  as={<TextInput />}
  control={control}
  name="name"
/>

formState

form内の入力の有無や送信の状態などを取得できる。formState.isSubmittingはbuttonにローディングをつける時によく使う。バージョン7でtouchedtouchedFieldsに改名されました。

名前 説明
isDirty 全体で何かしら変更があったらtrueになる boolean
dirtyFields それぞれの入力要素で何かしらの変更があったらtrue {name属性の値: boolean,...}
touchedFields それぞれの入力要素で何かしらの操作をしたらtrueになる
(フォーカスするだけでも、onBlurのタイミングで反映される)
{name属性の値: boolean,...}
isSubmitted formが送信されたらtrue、resetでfalseに戻る boolean
isSubmitSuccessful formの送信が成功したらtrue boolean
isSubmitting formの送信中にtrue boolean
submitCount formの送信された回数 number
isValid 何かしらエラーがあったらtrue
(modeがonChange or onBlurの時のみ)
boolean
isValidating バリデーション中かどうか? boolean
errors バリデーションのエラーのオブジェクトが入る {name属性の値: {
type: "required",
message: "",
ref: <input name="name属性の値" type="text">,
},...}
import { useForm } from "react-hook-form";

export const Demo = () => {
  const { register, handleSubmit, errors, formState } = useForm();
  const onSubmit = data => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="name" ref={register} />
      {isDirty && <p>Form is dirty.</p>}
      {isSubmitting && <span>Submitting...</span>}
      <button type="submit" />
    </form>
  );
}

Controller

UIライブラリを使う時に使用。registerだと参照できないことが多い。(MaterialUIのTextFieldとかはinputRefregisterを入れて参照できる)。ruleregisterの中に書く感じでバリデーションを書ける。defaultValueも設定可能
asの中に全部入れちゃうのが簡単

<Controller
  as={<TextInput />}
  control={control}
  name="name"
  rules={{required: true}}
  defaultValue="aaa"
/>

<Controller
  control={control}
  name="email"
  render={({
    field: { onChange, onBlur, value, name, ref },
    fieldState: { invalid, isTouched, isDirty, error },
  }) => (
    <TextField
      onChange={onChange}
      inputRef={ref}
            value={value}
    />
  )}
/>

ErrorMessage

指定したnameのエラーメッセージを表示できる。messageはstringならOK

import { ErrorMessage } from '@hookform/error-message';

<ErrorMessage errors={errors} name="email" message={errors.email?.message} />

useFormContext, FormProvider

FormProvideruseFormregistererrorsなどをまとめて渡して、ラップされたコンポーネントでuseFormContextを使うと、その渡された値を使うことができる。

import { useForm, FormProvider, useFormContext } from "react-hook-form";

export const Demo = () => {
  const methods = useForm();
  const on_submit = data => console.log(data);

  return (
    <FormProvider {...methods} >
      <form onSubmit={methods.handleSubmit(onSubmit)}>
        <Input />
        <button type="submit" />
      </form>
    </FormProvider>
  );
}

const Input = () => {
  const { register } = useFormContext();
  return <input {...register('name')} />;
}

useWatch

controlを渡すことでwatchを別のコンポーネントでも使えるようにした感じ。

import { useForm, useWatch } from "react-hook-form";

const Watch = ({ control }) => {
  const name = useWatch({ control, name: 'name', defaultValue: 'aaa' });

  return <div>{name}</div>;
}

type FieldInputs = {
    name: string;
}

export const Demo = () => {
  const { register, control, handleSubmit } = useForm<FieldInputs>();
  
  return (
    <form onSubmit={handleSubmit(data => console.log("data", data))}>
      <input ref={register} name="name" />
      <Watch control={control} />
      <button type="submit" />
    </form>
  );
}

useFieldArray

下の写真みたいな感じで、同じinputなどを増減させたい時に使えます。公式ドキュメントから、YoutubeのuseFieldArryの紹介動画に飛べるので見るとわかりやすいです。

image.png

↓こんな感じでuseFormのように色んなメソッドを使えます。nameとcontrolが必要です。defaultValueはuseFormで設定できます。

const { fields, append, prepend, remove, swap, move, insert } = useFieldArray({
    control,
    name: "demo"
});

↓3列目は例です。

fields mapを使ったりして、入力要素をレンダリングするのに使用。idやdefaultValueが入ったオブジェクト
append fieldsの最後にinputを追加 append({name: "aaa"})
prepend fieldsの先頭にinputを追加 prepend({name: "aaa"})
insert fieldsの特定の位置にinputを追加 insert(3,{name: "aaa"})
swap inputの位置を入れ替える swap(1,2)
move inputの位置を動かす move(1,2)
replace fieldsの値を置き換える replace({name: 'aaa'})
remove 特定のinputを削除、引数無しで全削除 remove(1) remove()
import { SubmitHandler, useForm, useFormState } from 'react-hook-form';

type Inputs = {
  firstName: string;
  lastName: string;
};

export default function Demo() {
  const { register, handleSubmit, control } = useForm<Inputs>({
    defaultValues: {
      firstName: 'firstName',
    },
  });
  const { dirtyFields } = useFormState({
    control,
    name: 'firstName',
  });
  const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('firstName')} />
      {dirtyFields.firstName && <p>Field is dirty.</p>}
      <input {...register('lastName')} />
      <input type="submit" />
    </form>
  );
}



useController

controllerと同じ感じで、nameとcontrolを渡してあげれば、UIコンポーネントの入力も取得できるようになり、rulesでバリデーションを記述できます。
他にも、meta内にisTouched、isDirty、invalidがあり、それぞれ、1回でも要素にタッチ(フォーカス)したか、値が入力の有無、エラーの有無がbooleanで入っているみたいです。

Controllerタグを使わなくていいのは機能とUI部分が分離できて嬉しいですね!

import React,{ FC } from 'react';
import { TextField } from '@material-ui/core';
import { useForm, useController, Control } from 'react-hook-form';

type Props = {
  control: Control<Record<string, any>>;
  name: string;
  rules: any;
};

const Input: FC<Props> = ({ control, name, rules }) => {
  const { field, meta } = useController({ control, name, rules });

  return (
    <>
      <TextField {...field} />
      <p>{meta.isTouched && 'Touched'}</p>
      <p>{meta.isDirty && 'Dirty'}</p>
      <p>{meta.invalid ? 'invalid' : 'valid'}</p>
    </>
  );
};

export default function App() {
  const { handleSubmit, control } = useForm({
    defaultValues: {
      firstName: '',
    },
  });
  const onSubmit = (data: { firstName: string }) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Input control={control} name="firstName" />
      <button type="submit">PUSH</button>
    </form>
  );
}

useFormState

useFormに入っているformStateをcontrollerやnameを引数に渡して、便利に使うことができます。

import { SubmitHandler, useForm, useFormState } from 'react-hook-form';

type Inputs = {
  firstName: string;
};

export default function Demo() {
  const { register, handleSubmit, control } = useForm<Inputs>({
    defaultValues: {
      firstName: 'firstName',
    },
  });
  const { dirtyFields } = useFormState({
    control,
  });
  const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('firstName')} />
      {dirtyFields.firstName && <p>Field is dirty.</p>}
      <input type="submit" />
    </form>
  );
}

終わりに

ここまで読んでいただきありがとうございます!
最近registerなどの型も追加されて、Typescriptでさらに書きやすくなりましたね!最近記事書けてなかったのでぼちぼち書いていきたいです。細かいオプションとかは使っていくうちに追記していければと思います。
質問、感想、改善点などをコメントでいただけるとモチベーションにつながりますのでよければお願いします!

296
198
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
296
198

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?