インストール
一つのインストールコマンドだけで、React Hook Form を使用する準備が整います。
npm install react-hook-form例
下記のコードは基本的な使用法を示します。
import React from "react"; import { useForm } from "react-hook-form"; export default function App() { const { register, handleSubmit, watch, errors } = useForm(); const onSubmit = data => console.log(data); console.log(watch("example")); // watch input value by passing the name of it return ( {/* "handleSubmit" will validate your inputs before invoking "onSubmit" */} <form onSubmit={handleSubmit(onSubmit)}> {/* register your input into the hook by invoking the "register" function */} <input name="example" defaultValue="test" ref={register} /> {/* include validation with required or other standard HTML validation rules */} <input name="exampleRequired" ref={register({ required: true })} /> {/* errors will return when field validation fails */} {errors.exampleRequired && <span>This field is required</span>} <input type="submit" /> </form> ); }
import React from "react"; import { useForm } from "react-hook-form"; type Inputs = { example: string, exampleRequired: string, }; export default function App() { const { register, handleSubmit, watch, errors } = useForm<Inputs>(); const onSubmit = data => console.log(data); console.log(watch("example")) // watch input value by passing the name of it return ( {/* "handleSubmit" will validate your inputs before invoking "onSubmit" */} <form onSubmit={handleSubmit(onSubmit)}> {/* register your input into the hook by invoking the "register" function */} <input name="example" defaultValue="test" ref={register} /> {/* include validation with required or other standard HTML validation rules */} <input name="exampleRequired" ref={register({ required: true })} /> {/* errors will return when field validation fails */} {errors.exampleRequired && <span>This field is required</span>} <input type="submit" /> </form> ); }
♦
ビデオチュートリアル
このビデオチュートリアルでは、React Hook Form の基本的な使用法とコンセプトを説明します。
フィールドを登録する
React Hook Form の重要なコンセプトの一つは、非制御コンポーネント (Uncontrolled Components) をフックに登録(register
) し、フォームフィールドの値を検証と収集できるようにすることです。
注意: 各フィールドには登録プロセスの key としてユニークな name
属性が必須です。
注意:React Native は手動登録 (manual register
) する必要があります。 (例: register({ name: 'test' }, { required: true })
または、Controllerを使用してコンポーネントをラップします。React Nativeセクションで詳細を読むこともできます。
import React from "react"; import { useForm } from "react-hook-form"; export default function App() { const { register, handleSubmit } = useForm(); const onSubmit = data => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input name="firstName" ref={register} /> <select name="gender" ref={register}> <option value="female">female</option> <option value="male">male</option> <option value="other">other</option> </select> <input type="submit" /> </form> ); }
import React from "react"; import ReactDOM from "react-dom"; import { useForm } from "react-hook-form"; enum GenderEnum { female = "female", male = "male", other = "other" } interface IFormInput { firstName: String; gender: GenderEnum; } export default function App() { const { register, handleSubmit } = useForm<IFormInput>(); const onSubmit = (data: IFormInput) => { console.log(data) }; return ( <form onSubmit={handleSubmit(onSubmit)}> <label>First Name</label> <input name="firstName" ref={register} /> <label>Gender Selection</label> <select name="gender" ref={register}> <option value="female">female</option> <option value="male">male</option> <option value="other">other</option> </select> <input type="submit" /> </form> ); }
バリデーションを適用する
React Hook Form は既存のHTML 標準のフォームバリデーション合わせることにより、フォームバリデーションを容易にします。
サポートされているバリデーションルール一覧:
required
min
max
minLength
maxLength
pattern
validate
register セクションで各ルールの詳細を読むことができます。
import React from "react"; import { useForm } from "react-hook-form"; export default function App() { const { register, handleSubmit } = useForm(); const onSubmit = data => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input name="firstName" ref={register({ required: true, maxLength: 20 })} /> <input name="lastName" ref={register({ pattern: /^[A-Za-z]+$/i })} /> <input name="age" type="number" ref={register({ min: 18, max: 99 })} /> <input type="submit" /> </form> ); }
import React from "react"; import { useForm } from "react-hook-form"; interface IFormInput { firstName: string; lastName: string; age: number; } export default function App() { const { register, handleSubmit } = useForm<IFormInput>(); const onSubmit = (data: IFormInput) => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input name="firstName" ref={register({ required: true, maxLength: 20 })} /> <input name="lastName" ref={register({ pattern: /^[A-Za-z]+$/i })} /> <input name="age" type="number" ref={register({ min: 18, max: 99 })} /> <input type="submit" /> </form> ); }
既存のフォームに適用する
既存のフォームの処理はシンプルです。重要なステップは、既存のコンポーネントのref
に register
を適用することです。import React from "react"; import { useForm } from "react-hook-form"; // The following component is an example of your existing Input Component const Input = ({ label, register, required }) => ( <> <label>{label}</label> <input name={label} ref={register({ required })} /> </> ); // you can use React.forwardRef to pass the ref too const Select = React.forwardRef(({ label }, ref) => ( <> <label>{label}</label> <select name={label} ref={ref}> <option value="20">20</option> <option value="30">30</option> </select> </> )); export default function App() { const { register, handleSubmit } = useForm(); const onSubmit = data => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <Input label="First Name" register={register} required /> <Select label="Age" ref={register} /> <input type="submit" /> </form> ); }
import React from "react"; import { useForm } from "react-hook-form"; type RefReturn = | string | ((instance: HTMLInputElement | null) => void) | React.RefObject<HTMLInputElement> | null | undefined; type InputProps = React.DetailedHTMLProps< React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement > & { label: string; register: ({ required }: { required?: boolean }) => RefReturn; }; interface IInputProps { label: string; } // The following component is an example of your existing Input Component const Input: React.FC<InputProps> = ({ label, register, required }) => ( <> <label>{label}</label> <input name={label} ref={register({ required })} /> </> ); type Option = { label: React.ReactNode; value: string | number | string[]; }; type SelectProps = React.DetailedHTMLProps< React.SelectHTMLAttributes<HTMLSelectElement>, HTMLSelectElement > & { options: Option[] } & HTMLSelectElement; // you can use React.forwardRef to pass the ref too const Select = React.forwardRef<SelectProps, { label: string }>( ({ label }, ref) => ( <> <label>{label}</label> <select name={label} ref={ref}> <option value="20">20</option> <option value="30">30</option> </select> </> ) ); interface IFormValues { "First Name": string; Age: number; } const App = () => { const { register, handleSubmit } = useForm<IFormValues>(); const onSubmit = (data: IFormValues) => { console.log(data) }; return ( <form onSubmit={handleSubmit(onSubmit)}> <Input label="First Name" register={register} required /> <Select label="Age" ref={register} /> <input type="submit" /> </form> ); };
UI ライブラリを使用する
React Hook Form は、外部 UI コンポーネントライブラリとの統合が容易です。
オプション1: 最適な方法は、使用したい外部コンポーネントが register
に使用できる innerRef
または ref
を公開しているかどうかを確認することです。 例えば、Material-UI の TextField
は、 props の1つとして inputRef
を受け付けます。inputRef
に register
を渡すだけです
<TextField inputRef={register} label="First name" name="FirstName"/>
オプション2: 例えば、 react-select
や react-datepicker
などのように、 コンポーネントによっては register のための prop が公開されていないことがあります。
次に簡単な方法は、ラッパーコンポーネントである Controller を使用することです。 このコンポーネントはカスタム登録処理を行います。
import React from "react"; import Select from "react-select"; import { useForm, Controller } from "react-hook-form"; import Input from "@material-ui/core/Input"; const App = () => { const { control, handleSubmit } = useForm(); const onSubmit = (data: IFormInput) => { console.log(data) }; return ( <form onSubmit={handleSubmit(onSubmit)}> <Controller name="firstName" control={control} defaultValue="" render={({ onChange, value }) => <input onChange={onChange} value={value} />} /> <Controller name="iceCreamType" control={control} options={[ { value: "chocolate", label: "Chocolate" }, { value: "strawberry", label: "Strawberry" }, { value: "vanilla", label: "Vanilla" } ]} as={Select} /> <input type="submit" /> </form> ); };
import React from "react"; import Select from "react-select"; import { useForm, Controller } from "react-hook-form"; import Input from "@material-ui/core/Input"; interface IFormInput { firstName: string; lastName: string; iceCreamType: string; } const App = () => { const { control, handleSubmit } = useForm<IFormInput>(); const onSubmit = (data: IFormInput) => { console.log(data) }; return ( <form onSubmit={handleSubmit(onSubmit)}> <Controller name="firstName" control={control} defaultValue="" render={({ onChange, value }) => <input onChange={onChange} value={value} />} /> <Controller name="iceCreamType" control={control} options={[ { value: "chocolate", label: "Chocolate" }, { value: "strawberry", label: "Strawberry" }, { value: "vanilla", label: "Vanilla" } ]} as={Select} /> <input type="submit" /> </form> ); };
オプション3: 最後に useEffect フックを使用してカスタム登録を設定し、 setValue
を介して値を更新できます。
import React from "react"; import { useForm } from "react-hook-form"; import Select from "react-select"; import Input from "@material-ui/core/Input"; import { Input as InputField } from "antd"; export default function App() { const { register, handleSubmit, setValue } = useForm(); const onSubmit = data => console.log(data); const handleChange = (e) => { setValue("AntdInput", e.target.value); } React.useEffect(() => { register("AntdInput"); // custom register Antd input }, [register]) return ( <form onSubmit={handleSubmit(onSubmit)}> <InputField name="name" onChange={handleChange} /> <input type="submit" /> </form> ); }
import React from "react"; import { useForm } from "react-hook-form"; import Select from "react-select"; import Input from "@material-ui/core/Input"; import { Input as InputField } from "antd"; interface IFormInput { name: string } export default function App() { const { register, handleSubmit, setValue } = useForm(); const onSubmit = data => console.log(data); const handleChange = (e) => { setValue("AntdInput", e.target.value); } React.useEffect(() => { register("AntdInput"); // custom register Antd input }, [register]) return ( <form onSubmit={handleSubmit(onSubmit)}> <InputField name="name" onChange={handleChange} /> <input type="submit" /> </form> ); }
制御された Input
React Hook Form は、非制御コンポーネントとネイティブ HTML input をサポートしますが、 React-Select や AntD 、 Material-UI などの外部の制御された UI コンポーネントライブラリと組み合わせての使用を避けることは難しいため、 ラッパーコンポーネントを作成しました。 Controller は、必要に応じてカスタム登録を自由に使用できると同時に、統合プロセスを簡素化します。
import React from "react"; import { useForm, Controller } from "react-hook-form"; import ReactSelect from "react-select"; import { TextField, Checkbox } from "@material-ui/core"; function App() { const methods = useForm(); const { handleSubmit, control, reset } = methods; const onSubmit = data => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> {/* Option 1: pass a component to the Controller. */} <Controller as={TextField} name="TextField" control={control} defaultValue="" /> {/* Option 2: use render props to assign events and value */} <Controller name="MyCheckbox" control={control} defaultValue={false} rules={{ required: true }} render={props => <Checkbox onChange={e => props.onChange(e.target.checked)} checked={props.value} /> } // props contains: onChange, onBlur and value /> </form> ); }
import React from "react"; import { useForm, Controller } from "react-hook-form"; import ReactSelect from "react-select"; import { TextField, Checkbox } from "@material-ui/core"; interface IFormInputs { TextField: string MyCheckbox: boolean } function App() { const methods = useForm<IFormInputs>(); const { handleSubmit, control, reset } = methods; const onSubmit = (data: IFormInputs) => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> {/* Option 1: pass a component to the Controller. */} <Controller as={TextField} name="TextField" control={control} defaultValue="" /> {/* Option 2: use render props to assign events and value */} <Controller name="MyCheckbox" control={control} defaultValue={false} rules={{ required: true }} render={props => <Checkbox onChange={e => props.onChange(e.target.checked)} checked={props.value} /> } // props contains: onChange, onBlur and value /> </form> ); }
グローバルな状態に統合する
React Hook Form では、データを保存するために状態管理ライブラリを使用する必要はありませんが、簡単に統合することができます。import React from "react"; import { useForm } from "react-hook-form"; import { connect } from "react-redux"; import updateAction from "./actions"; export default function App(props) { const { register, handleSubmit, setValue } = useForm(); // Submit your data into Redux store const onSubmit = data => props.updateAction(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <Input name="firstName" defaultValue={props.firstName} ref={register} /> <Input name="lastName" defaultValue={props.lastName} ref={register} /> <input type="submit" /> </form> ); } // Connect your component with redux connect(({ firstName, lastName }) => ({ firstName, lastName }), updateAction)(YourForm);
エラーを処理する
React Hook Form はフォーム内のエラーを表すerrors
オブジェクトを提供しています。import React from "react"; import { useForm } from "react-hook-form"; export default function App() { const { register, errors, handleSubmit } = useForm(); return ( <form onSubmit={handleSubmit(onSubmit)}> <Input name="firstName" ref={register({ required: true })} /> {errors.firstName && "First name is required"} <Input name="lastName" ref={register({ required: true })} /> {errors.lastName && "Last name is required"} <input type="submit" /> </form> ); }
import React from "react"; import { useForm } from "react-hook-form"; interface IFormInputs { firstName: string lastName: string } export default function App() { const { register, errors, handleSubmit } = useForm<IFormInputs>(); return ( <form onSubmit={handleSubmit(onSubmit)}> <Input name="firstName" ref={register({ required: true })} /> {errors.firstName && "First name is required"} <Input name="lastName" ref={register({ required: true })} /> {errors.lastName && "Last name is required"} <input type="submit" /> </form> ); }
スキーマバリデーション
React Hook Form は、 Yup, Superstruct & Joi を活用してスキーマベースのフォームバリデーションをサポートしています。 省略可能な引数として validationSchema
を useForm に渡すことができます。 React Hook Form は、入力されたデータをスキーマに対してバリデーションを行い、 errors や妥当な結果を返します。
ステップ1: Yup
をプロジェクトにインストールします。
ステップ2: バリデーション用のスキーマを作成し、 React Hook Form を使用して input を登録します。
import React from "react"; import { useForm } from "react-hook-form"; import { yupResolver } from '@hookform/resolvers/yup'; import * as yup from "yup"; const schema = yup.object().shape({ firstName: yup.string().required(), age: yup.number().positive().integer().required(), }); export default function App() { const { register, handleSubmit, errors } = useForm({ resolver: yupResolver(schema) }); const onSubmit = data => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input type="text" name="firstName" ref={register} /> <p>{errors.firstName?.message}</p> <input type="text" name="age" ref={register} /> <p>{errors.age?.message}</p> <input type="submit" /> </form> ); }
import React from "react"; import { useForm } from "react-hook-form"; import { yupResolver } from '@hookform/resolvers/yup'; import * as yup from "yup"; interface IFormInputs { firstName: string age: number } const schema = yup.object().shape({ firstName: yup.string().required(), age: yup.number().positive().integer().required(), }); export default function App() { const { register, handleSubmit, errors } = useForm<IFormInputs>({ resolver: yupResolver(schema) }); const onSubmit = (data: IFormInputs) => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input type="text" name="firstName" ref={register} /> <p>{errors.firstName?.message}</p> <input type="text" name="age" ref={register} /> <p>{errors.age?.message}</p> <input type="submit" /> </form> ); }
React Native
非制御コンポーネントでも、同じようにパフォーマンスが向上します。 ただし、React Native と互換性のない API がいくつかあります (Web とネイティブとの API の違い)。 下記の例に示すように、手動登録 (manual register
) を使用する必要があります。
import React from "react"; import { Text, View, TextInput, Button, Alert } from "react-native"; import { useForm, Controller } from "react-hook-form"; export default function App() { const { control, handleSubmit, errors } = useForm(); const onSubmit = data => console.log(data); return ( <View> <Controller control={control} render={({ onChange, onBlur, value }) => ( <TextInput style={styles.input} onBlur={onBlur} onChangeText={value => onChange(value)} value={value} /> )} name="firstName" rules={{ required: true }} defaultValue="" /> {errors.firstName && <Text>This is required.</Text>} <Controller control={control} render={({ onChange, onBlur, value }) => ( <TextInput style={styles.input} onBlur={onBlur} onChangeText={value => onChange(value)} value={value} /> )} name="lastName" defaultValue="" /> <Button title="Submit" onPress={handleSubmit(onSubmit)} /> </View> ); }
TypeScript
React Hook Form は TypeScript
を使用して構築されているため、フォームの値をサポートするための FormData
型を定義することができます。
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>(); const onSubmit = handleSubmit(({ firstName, lastName }) => { console.log(firstName, lastName); }); // firstName and lastName will have correct type 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("lastName", "luo"); // ✅ setValue("firstName", true); // ❌: true is not string errors.bill; // ❌: property bill does not exist }} > SetValue </button> </form> ); }
もっと知りたいですか?
React Hook Form のドキュメントを見て、API に関する全ての情報を確認してください。