前回の続きです。
基本的な使い方
バリデーションなどを無視するなら、基本的にはフォームの要素のref属性にuseForm
から得られるregister
関数を登録し、formのonSubmit
にhandleSubmit
を登録するだけで利用できます。
あえてフォームを作る順序を書くなら
- フォームの要素を書き出す
- フィールドを登録する
- スタイリング(ここでは取り扱わない)
という流れになるかと思います。最初から順にやっていきます。
フォームの要素を書き出す
基本的には普通のHTMLとなんら変わり無い要素をReactで表現します。
import React from "react" export default function App() { return ( <form> <input name="firstName" /> <select name="gender"> <option value="male">male</option> <option value="female">female</option> </select> <input type="submit" /> </form> ) }
非常にシンプルなフォームですが、今回はこれに機能を実装していきます。
フィールドを登録する
ここでは
- input(name)
- select(gender)
という2種類のフォームの要素があります。これらをReact Hook Formのフィールドに登録していきます。
import React from "react" import { useForm } from "react-hook-form" // useFormをreact-hook-formからインポート export default function App() { const { register, handleSubmit } = useForm() // register, handleSubmitをuseFormというHooksから得る const onSubmit = (data: any) => console.log(data) // フォームが送信されたときに実行される関数 return ( // form要素にhandleSubmitを渡す。なお、引数にはフォーム送信時に実行される関数を渡す。 <form onSubmit={handleSubmit(onSubmit)}> {/* input 要素にref属性を付与、registerを渡す*/} <input name="firstName" ref={register} /> {/* select 要素にref属性を付与、registerを渡す*/} <select name="gender" ref={register}> <option value="male">male</option> <option value="female">female</option> </select> <input type="submit" /> </form> ) }
基本的にはこれだけでフォームが利用可能です。
フォームのデータを利用してなにがしかしたいときはonSubmit
の中に処理を書いていきます。
心がTSに支配されてる方は(data: any)
で発狂しそうかと思いますが、後で言及するので我慢して下さい。僕も発狂寸前なんです。
非同期処理
おそらくフォームの送信の後にはAPIと通信したりするので非同期処理が来ると思います。
上記のhandleSubmit
の引数にはPromiseも渡せるので、async関数がそのまま渡せます。
import React from "react" import { useForm } from "react-hook-form" const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) export default function App() { const { register, handleSubmit } = useForm() const onSubmit = async (data: any) => { console.log(data) await sleep(1000) console.log("complete.") } return ( <form onSubmit={handleSubmit(onSubmit)}> <input name="firstName" ref={register} /> <select name="gender" ref={register}> <option value="male">male</option> <option value="female">female</option> </select> <input type="submit" /> </form> ) }
バリデーションを適応する
基本的には上記までで良さそうなものですが、バリデーションぐらいはフロントでもやっておくれ、という気持ちが湧いてきます。
React Hooks FormはHTML 標準のフォームバリデーションのバリデーションには対応しているようです。
対応しているルールは公式サイトを閲覧してもらえればと思いますが、大雑把に言えば
- 入力必須
- 文字数の長さ
- 正規表現
などのルールを適応し、バリデーションに通らない場合は即座にエラーを得ることができるようです。
import React from "react" import { useForm } from "react-hook-form" export default function App() { const { register, handleSubmit, errors } = useForm() const onSubmit = (data: any) => console.log(data) return ( <form onSubmit={handleSubmit(onSubmit)}> <input name="firstName" ref={register({ required: true })} /> {errors.firstName && "First name is required"} <input name="lastName" ref={register({ pattern: /^[A-Za-z]+$/i })} /> {errors.lastName && "Last name is not mached [A-Za-z]"} <input name="age" type="number" ref={register({ min: 18, max: 99 })} /> {errors.age && "age is in ragnge 18-99"} <input type="submit" /> </form> ) }
errors.
の後にname
属性で決めたプロパティを参照するとバリデーションのルールを適応した可否が得られます。
上記の例ではboolean &&
の形で、もしバリデーションが通ってなければ警告文を表示する様な形式にしています。
バリデーションに適合していなければ、送信してもonSubmit
が実行されません。
UIライブラリの利用
一応方法としては
- 対応するUIライブラリ(material-uiなど)を利用する
- Controllerを利用してカスタム登録処理をする
- useEffectを利用する
の3種類ありますが、自分は基本的にmaterial-uiしか利用しないので上記2点にのみ言及します。
対応するUIライブラリを利用する
これが最も簡単です。material-uiを利用している人は、属性を追加するだけで利用できます。
まずはmaterial-uiをインストールします。
yarn add @material-ui/core
もうこれでフォームが作成可能です。
ソースを変更していきます。
import React from "react" import { useForm } from "react-hook-form" import TextField from "@material-ui/core/TextField" export default function App() { const { register, handleSubmit } = useForm() const onSubmit = (data: any) => console.log(data) return ( <form onSubmit={handleSubmit(onSubmit)}> <TextField inputRef={register} label="First name" name="firstName" /> <input type="submit" /> </form> ) }
これで、TextField
コンポーネントに入力された値がonSubmit
のdata
の中に放り込まれます。
Controllerを利用してカスタム登録処理をする
テキストフィールドやチェックボックスぐらいであれば上記の処理で問題無いんですが、ラジオボタンなどのフォーム要素が制御されたもの等を利用する場合は上記の用にはいきません。
例えば、ラジオボタンの例を出せば
import React, { useState } from "react" import { useForm } from "react-hook-form" import { TextField, FormControl, FormLabel, FormControlLabel, Radio, RadioGroup, } from "@material-ui/core" export default function App() { const { register, handleSubmit } = useForm() const onSubmit = (data: any) => console.log(data) const radioList: string[] = ["male", "female", "other"] const [value, setValue] = useState(radioList[0]) const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { setValue((event.target as HTMLInputElement).value) } return ( <form onSubmit={handleSubmit(onSubmit)}> <TextField inputRef={register} label="First name" name="firstName" /> <FormControl> <FormLabel>Gender</FormLabel> <RadioGroup name="gender1" value={value} onChange={handleChange}> {radioList.map(item => ( <FormControlLabel key={item} value={item} control={<Radio />} label={item} /> ))} </RadioGroup> </FormControl> <input type="submit" /> </form> ) }
といった場合にはinputRef
にregister
を登録すればOKという訳にもいきません。
そこで、React Hook FormにはController
というものが用意されています。
import React from "react" import { useForm, Controller } from "react-hook-form" import { TextField, FormLabel, FormControlLabel, Radio, RadioGroup, } from "@material-ui/core" const radioList: string[] = ["male", "female", "other"] export default function App() { const { register, handleSubmit, control } = useForm() const onSubmit = (data: any) => console.log(data) return ( <form onSubmit={handleSubmit(onSubmit)}> <TextField inputRef={register} label="First name" name="firstName" /> {/* 上記と同じ、Controllerを利用してTextFieldを使い場合の例 */} <Controller name="firstName2" as={TextField} control={control} defaultValue="" /> {/* FormControlのセクションを<div></div>と想定 */} <div> <FormLabel>Gender</FormLabel> <Controller name="radioGroup" as={ <RadioGroup name="gender1"> {radioList.map(item => ( <FormControlLabel key={item} value={item} control={<Radio />} label={item} /> ))} </RadioGroup> } control={control} defaultValue={radioList[0]} /> </div> <input type="submit" /> </form> ) }
やや複雑になりましたが、<FormControl></FormControl>
の例と比べても、そこまで記述量が増えていないにもかかわらずフォームデータのやりとりができるのが確認できます。
なお、ここではしれっとdefaultValue
を指定していますが、こいつを指定していないとMaterial-UIはコンパイル時にエラーを吐きます。
(たぶんa component is changing an uncontrolled radiogroup to be controlled react hook form
みたいなやつ)
フォームの要素を記述する時にdefaultValue={}
として値を指定するか、useForm
の歳に引数に初期値を入力するかのどちらかで対応して下さい。
TypeScriptの対応
上記までは、onSubmit
にany型をたたき込んだりとTypeScriptの良さを出し切れてない使い方でしたが、useForm
に型を適応することでuseForm
から得られるsetValue
やerrors
に型を適応することができます。
import * as React from "react" import { useForm } from "react-hook-form" // フォームにあるフィールドの型の定義 type FormData = { firstName: string lastName: string } export default function App() { const { register, setValue, handleSubmit, errors } = useForm<FormData>() // フィールドの型定義をuseFormに適応する // handleSubmitの引数にはフィールドの型定義が適応されている const onSubmit = handleSubmit(({ firstName, lastName }) => { console.log(firstName, lastName) }) return ( <form onSubmit={onSubmit}> <label>First Name</label> <input name="firstName" ref={register} /> <label>Last Name</label> <input name="lastName" ref={register} /> <button type="button" onClick={() => { // setValueにも型定義が適応されている setValue("lastName", "luo") // OK setValue("firstName", true) // コンパイルエラー errors.bill // errorsにも型定義が適応されているのでコンパイルエラー }} > SetValue </button> </form> ) }