インストール
一つのインストールコマンドだけで、React Hook Form を使用する準備が整います。
npm install react-hook-form例
下記のコードは基本的な使用法を示します。
import { useForm } from "react-hook-form"; export default function App() { const { register, handleSubmit, watch, formState: { 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 defaultValue="test" {...register("example")} /> {/* include validation with required or other standard HTML validation rules */} <input {...register("exampleRequired", { required: true })} /> {/* errors will return when field validation fails */} {errors.exampleRequired && <span>This field is required</span>} <input type="submit" /> </form> ); }
import { useForm, SubmitHandler } from "react-hook-form"; type Inputs = { example: string, exampleRequired: string, }; export default function App() { const { register, handleSubmit, watch, formState: { errors } } = useForm<Inputs>(); const onSubmit: SubmitHandler<Inputs> = 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 defaultValue="test" {...register("example")} /> {/* include validation with required or other standard HTML validation rules */} <input {...register("exampleRequired", { 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('test', { required: true })
または、Controllerを使用してコンポーネントをラップします。React Nativeセクションで詳細を読むこともできます。
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 {...register("firstName")} /> <select {...register("gender")}> <option value="female">female</option> <option value="male">male</option> <option value="other">other</option> </select> <input type="submit" /> </form> ); }
import ReactDOM from "react-dom"; import { useForm, SubmitHandler } 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: SubmitHandler<IFormInput> = data => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <label>First Name</label> <input {...register("firstName")} /> <label>Gender Selection</label> <select {...register("gender")} > <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 { 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 {...register("firstName", { required: true, maxLength: 20 })} /> <input {...register("lastName", { pattern: /^[A-Za-z]+$/i })} /> <input type="number" {...register("age", { min: 18, max: 99 })} /> <input type="submit" /> </form> ); }
import { useForm, SubmitHandler } from "react-hook-form"; interface IFormInput { firstName: string; lastName: string; age: number; } export default function App() { const { register, handleSubmit } = useForm<IFormInput>(); const onSubmit: SubmitHandler<IFormInput> = data => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register("firstName", { required: true, maxLength: 20 })} /> <input {...register("lastName", { pattern: /^[A-Za-z]+$/i })} /> <input type="number" {...register("age", { min: 18, max: 99 })} /> <input type="submit" /> </form> ); }
既存のフォームに適用する
既存のフォームの処理はシンプルです。重要なステップは、既存のコンポーネントのref
に register
を適用することです。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 {...register(label, { required })} /> </> ); // you can use React.forwardRef to pass the ref too const Select = React.forwardRef(({ onChange, onBlur, name, label }, ref) => ( <> <label>{label}</label> <select name={name} ref={ref} onChange={onChange} onBlur={onBlur}> <option value="20">20</option> <option value="30">30</option> </select> </> )); const App = () => { const { register, handleSubmit } = useForm(); const onSubmit = (data) => { alert(JSON.stringify(data)); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <Input label="First Name" register={register} required /> <Select label="Age" {...register("Age")} /> <input type="submit" /> </form> ); };
import { Path, useForm, UseFormRegister, SubmitHandler } from "react-hook-form"; interface IFormValues { "First Name": string; Age: number; } type InputProps = { label: Path<IFormValues>; register: UseFormRegister<IFormValues>; required: boolean; }; // The following component is an example of your existing Input Component const Input = ({ label, register, required }: InputProps) => ( <> <label>{label}</label> <input {...register(label, { required })} /> </> ); // you can use React.forwardRef to pass the ref too const Select = React.forwardRef< HTMLSelectElement, { label: string } & ReturnType<UseFormRegister<IFormValues>> >(({ onChange, onBlur, name, label }, ref) => ( <> <label>{label}</label> <select name={name} ref={ref} onChange={onChange} onBlur={onBlur}> <option value="20">20</option> <option value="30">30</option> </select> </> )); const App = () => { const { register, handleSubmit } = useForm<IFormValues>(); const onSubmit: SubmitHandler<IFormValues> = data => { alert(JSON.stringify(data)); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <Input label="First Name" register={register} required /> <Select label="Age" {...register("Age")} /> <input type="submit" /> </form> ); };
UI ライブラリを使用する
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({ defaultValues: { firstName: '', select: {} } }); const onSubmit = data => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <Controller name="firstName" control={control} render={({ field }) => <Input {...field} />} /> <Controller name="select" control={control} render={({ field }) => <Select {...field} options={[ { value: "chocolate", label: "Chocolate" }, { value: "strawberry", label: "Strawberry" }, { value: "vanilla", label: "Vanilla" } ]} />} /> <input type="submit" /> </form> ); };
import Select from "react-select"; import { useForm, Controller, SubmitHandler } from "react-hook-form"; import Input from "@material-ui/core/Input"; interface IFormInput { firstName: string; lastName: string; iceCreamType: {label: string; value: string }; } const App = () => { const { control, handleSubmit } = useForm<IFormInput>(); const onSubmit: SubmitHandler<IFormInput> = data => { console.log(data) }; return ( <form onSubmit={handleSubmit(onSubmit)}> <Controller name="firstName" control={control} defaultValue="" render={({ field }) => <Input {...field} />} /> <Controller name="iceCreamType" control={control} render={({ field }) => <Select {...field} options={[ { value: "chocolate", label: "Chocolate" }, { value: "strawberry", label: "Strawberry" }, { value: "vanilla", label: "Vanilla" } ]} />} /> <input type="submit" /> </form> ); };
制御された Input
React Hook Form は、非制御コンポーネントとネイティブ HTML input をサポートしますが、 React-Select や AntD 、 MUI などの外部の制御された UI コンポーネントライブラリと組み合わせての使用を避けることは難しいため、 ラッパーコンポーネントを作成しました。 Controller は、必要に応じてカスタム登録を自由に使用できると同時に、統合プロセスを簡素化します。
import { useForm, Controller } from "react-hook-form"; import { TextField, Checkbox } from "@material-ui/core"; function App() { const { handleSubmit, control, reset } = useForm({ defaultValues: { checkbox: false, } }); const onSubmit = data => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <Controller name="checkbox" control={control} rules={{ required: true }} render={({ field }) => <Checkbox {...field} />} /> <input type="submit" /> </form> ); }
import { useForm, Controller, SubmitHandler } from "react-hook-form"; import { TextField, Checkbox } from "@material-ui/core"; interface IFormInputs { TextField: string MyCheckbox: boolean } function App() { const { handleSubmit, control, reset } = useForm<IFormInputs>(); const onSubmit: SubmitHandler<IFormInputs> = data => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <Controller name="MyCheckbox" control={control} defaultValue={false} rules={{ required: true }} render={({ field }) => <Checkbox {...field} />} /> <input type="submit" /> </form> ); }
グローバルな状態に統合する
React Hook Form では、データを保存するために状態管理ライブラリを使用する必要はありませんが、簡単に統合することができます。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 {...register("firstName")} defaultValue={props.firstName} /> <input {...register("lastName")} defaultValue={props.lastName} /> <input type="submit" /> </form> ); } // Connect your component with redux connect(({ firstName, lastName }) => ({ firstName, lastName }), updateAction)(YourForm);
エラーを処理する
React Hook Form はフォーム内のエラーを表すerrors
オブジェクトを提供しています。import { useForm } from "react-hook-form"; export default function App() { const { register, formState: { errors }, handleSubmit } = useForm(); const onSubmit = (data) => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register("firstName", { required: true })} aria-invalid={errors.firstName ? "true" : "false"} /> {errors.firstName?.type === 'required' && <p role="alert">First name is required</p>} <input {...register("mail", { required: "Email Address is required" })} aria-invalid={errors.mail ? "true" : "false"} /> {errors.mail && <p role="alert">{errors.mail?.message}</p>} <input type="submit" /> </form> ); }
import { useForm, SubmitHandler } from "react-hook-form"; interface IFormInputs { firstName: string lastName: string } const onSubmit: SubmitHandler<IFormInputs> = data => console.log(data); export default function App() { const { register, formState: { errors }, handleSubmit } = useForm<IFormInputs>(); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register("firstName", { required: true })} /> {errors.firstName && "First name is required"} <input {...register("lastName", { 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 { useForm } from "react-hook-form"; import { yupResolver } from '@hookform/resolvers/yup'; import * as yup from "yup"; const schema = yup.object({ firstName: yup.string().required(), age: yup.number().positive().integer().required(), }).required(); export default function App() { const { register, handleSubmit, formState:{ errors } } = useForm({ resolver: yupResolver(schema) }); const onSubmit = data => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register("firstName")} /> <p>{errors.firstName?.message}</p> <input {...register("age")} /> <p>{errors.age?.message}</p> <input type="submit" /> </form> ); }
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({ firstName: yup.string().required(), age: yup.number().positive().integer().required(), }).required(); export default function App() { const { register, handleSubmit, formState: { errors } } = useForm<IFormInputs>({ resolver: yupResolver(schema) }); const onSubmit = (data: IFormInputs) => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register("firstName")} /> <p>{errors.firstName?.message}</p> <input {...register("age")} /> <p>{errors.age?.message}</p> <input type="submit" /> </form> ); }
React Native
非制御コンポーネントでも、同じようにパフォーマンスが向上します。 ただし、React Native と互換性のない API がいくつかあります (Web とネイティブとの API の違い)。 下記の例に示すように、手動登録 (manual register
) を使用する必要があります。
import { Text, View, TextInput, Button, Alert } from "react-native"; import { useForm, Controller } from "react-hook-form"; export default function App() { const { control, handleSubmit, formState: { errors } } = useForm({ defaultValues: { firstName: '', lastName: '' } }); const onSubmit = data => console.log(data); return ( <View> <Controller control={control} rules={{ required: true, }} render={({ field: { onChange, onBlur, value } }) => ( <TextInput style={styles.input} onBlur={onBlur} onChangeText={onChange} value={value} /> )} name="firstName" /> {errors.firstName && <Text>This is required.</Text>} <Controller control={control} rules={{ maxLength: 100, }} render={({ field: { onChange, onBlur, value } }) => ( <TextInput style={styles.input} onBlur={onBlur} onChangeText={onChange} value={value} /> )} name="lastName" /> <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, formState: { errors } } = useForm<FormData>(); const onSubmit = handleSubmit(data => console.log(data)); // firstName and lastName will have correct type return ( <form onSubmit={onSubmit}> <label>First Name</label> <input {...register("firstName")} /> <label>Last Name</label> <input {...register("lastName")} /> <button type="button" onClick={() => { setValue("lastName", "luo"); // ✅ setValue("firstName", true); // ❌: true is not string errors.bill; // ❌: property bill does not exist }} > SetValue </button> </form> ); }
Design and philosophy
React Hook Form's design and philosophy focus on user and developer experience. The library aims to provide users with a smoother interaction experience by fine-tuning the performance and improving accessibility. Some of the performance enhancements included
Introducing form state subscription model through the proxy
Avoiding unnecessary computation
Isolating component re-rendering when requires
Overall, it improves the user experience while users interact with the application. As for the developers, we introduce build-in validation and are closely aligned with HTML standards, extend it further with powerful validation methods and integrate it with schema validation natively. In addition, having a strongly type-checked form with the help of typescript provides early build-time feedback to help and guide the developer to build a robust form solution.
The following talk given by Bill showcased some of the ideas and design patterns:
もっと知りたいですか?
React Hook Form のドキュメントを見て、API に関する全ての情報を確認してください。