83
45

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

react-hook-formの使い方を解説 v6.13.0 useController追加!

Last updated at Posted at 2020-11-20

久しぶりの投稿になります。今後は週一記事以上は書けるように頑張ります!今回はreact-hook-formについての記事です。公式ドキュメントの内容を、自分なりにまとめてみました。より詳しく知りたい方は公式ドキュメントも見てみて下さい!
追記(2020/11/28):react-hook-formのv6.12.0で便利そうな機能がリリースされたので、それもregisterの部分に追加しました。

追記 (2021/09/29) version7以降の記事を別で書きました!
react-hook-formの使い方まとめ

参考:公式ドキュメント

react-hook-formとは

inputとかのformに関係するデータを使う時に、Stateを使わなくて良くて、レンダリング回数を減らせる!

インストール

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

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

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

↓↓↓簡単な例

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

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

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

  const on_submit = (data: FormInputs) => {
    console.log(data);
    reset();
  };

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

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

{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ライブラリを使う時
shouldUnregister 入力要素がアンマウントされたら、値の参照を解除、falseなら入力された値を保持
(初期値: true)
true or false

register

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

↓nameの付け方によって、dataの形が変わる.

name="firstName" →{ firstName: 'value'}
name="firstName[0]" →{ firstName: [ 'value' ] }
name="name.firstName" →{ name: { firstName: 'value' } }
name="name.firstName[0]" →{ name: { firstName: [ 'value' ] } }

引数にオブジェクトで色んなバリデーションが設定できる.

required 必須項目にするか true or false or エラーメッセージ
maxLength 最大文字数 {value: number, message: エラーメッセージ}
minLenght 最小文字数 {value: number, message: エラーメッセージ}
max 最大データ量 {value: number, message: エラーメッセージ}
min 最小データ量 {value: number, message: エラーメッセージ}
pattern 文字の形式 {value: 正規表現, message: エラーメッセージ}
validate カスタム {任意の名前: value => booleanを返す式 or 関数 || エラーメッセージ}
valueAsNumber new! 数値を返す、問題があればNaNを返す true or false
valueAsDate new! 日付を返す、問題があればnullを返す true or false
setValueAs new! 関数に値を通す 例)setValueAs:(value) => trim(value)
<input
   name="email"
   ref={register({
   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>

errors

registerなどで設定したバリデーションを通らなかった時にエラーのデータが入る。エラーメッセージを設定している場合は、それも取得できる。

<input name="name" ref={register({required: '入力して下さい'}) />
{errors.name && <p>errors.name.message</p>}

watch

第一引数入れたnameの値を監視。第二引数は初期値を設定可能。unregisterと同様に配列に入れて複数も可能。何も入れないと、全てのnameの値を取得。レンダリング回数は増える。

const watch_name = watch("name", false);
const watch_all_fields = watch();
const watch_fields = watch(["name", "email"]);

handleSubmit

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

const on_submit = (data: FormInputs) => {
    console.log(data);
};

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

###reset

formの中の状態を初期化する関数。defaultValueが設定されていたら、その値になる

const on_click =()=>{
  reset();
}

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 single_value = getValues("name");
const multiple_values = getValues(["name", "email"]);

setValue

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

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

trigger

引数に入れたnameのバリデーションを実行する関数。こちらも配列で複数、空なら全体。

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

control

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

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

formState

form内の入力の有無や送信の状態などを取得できる。formState.isSubmittingはbuttonにローディングをつける時によく使う。

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

export const Demo = () => {
  const { register, handleSubmit, errors, formState } = useForm();
  const on_submit = 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={({ onChange, onBlur, value, name, ref }) => (
    <TextField
      onBlur={onBlur}
      onChange={onChange}
      checked={value}
      inputRef={ref}
    />
  )}
/>

ErrorMessage

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

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

useFormContext,FormProvider

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

import React from "react";
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 name="name" ref={register} />;
}

useWatch

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

import React from "react";
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)
remove 特定のinputを削除、引数無しで全削除 remove(1) remove()
import React from 'react';
import { useForm, useFieldArray } from 'react-hook-form';

export const Demo = () => {
  const { register, control, handleSubmit } = useForm({
    defaultValues: {
      demo: [{ name: 'name', email: 'email@email.com' }],
    },
  });
  const { fields, append, prepend, remove } = useFieldArray({
    control,
    name: 'demo',
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      {fields.map((item, index) => (
        <div key={item.id}>
          <input
            name={`demo[${index}].name`}
            ref={register}
            defaultValue={item.name}
          />
          <input
            name={`demo[${index}].email`}
            ref={register}
            defaultValue={item.email}
          />
          <button type="button" onClick={() => remove(index)}>
            Delete
          </button>
        </div>
      ))}
      <button
        type="button"
        onClick={() =>
          append({ name: 'appendName', email: 'append@email.com' })
        }
      >
        append
      </button>
      <button
        type="button"
        onClick={() =>
          prepend({
            name: 'prependName',
            email: 'prepend@email.com',
          })
        }
      >
        prepend
      </button>
      <input type="submit" />
    </form>
  );
};


useController new!

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" rules={{ required: true }} />
      <button type="submit">PUSH</button>
    </form>
  );
}

終わりに

ここまで読んでいただきありがとうございます!
少しでもreact-hook-formの理解が深まれば幸いです。自分も書きながら、react-hook-formの便利さが機能が理解できました。
質問、感想、改善点などをコメントでいただけるとモチベーションにつながりますのでよければお願いします!

83
45
0

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
83
45

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?