Open9
react-hook-form
- useForm
- register: バリデーションルールを追加
- handleSubmit: フォームが送信されるときに実行される関数
- watch: 特定のフォームの入力フィールドの値を監視
- formState: フォームの状態の情報を保持
- errors
- isDirty
- isValid
- isSubmitting
get started
動画わかりやすい
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>
)
}
One of the key concepts in React Hook Form is to register your component into the hook
コンポ化した場合
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>
)
}
2種類あるってことか…?register渡すパターンとrefをforwardするパターン?
- registerパターン
- 名前付き(register実行後の結果を渡す)
- 親でregisterを実行して渡す
- refが生成されている
- 名前なし(register実行前の関数を渡す)
- 型を渡す必要あり
- 子でregisterを実行する
- refはない(その対象DOMで実行するから)
- 名前付き(register実行後の結果を渡す)
- controlパターン
- useWatchの引数にcontrolが必要?
- controlからregisterが取り出せるので、registerの上位互換?
-
Controller:非制御コンポを制御コンポにする?制御コンポ的に扱う?
- UIライブラリでRefを渡せない系用
- 既存PJからの以降でForwardRefのないコンポや、制御コンポライブラリをラップしてRHFで使えるようにする
If the component doesn't expose input's ref, then you should use the Controller component, which will take care of the registration process.
- Controllerコンポで囲って
render={({ field }) => <対象コンポ {...field} />}
の形 - Controllerコンポを使わず、
const { field, fieldState } = useController(props)
hooksを使う方法も(controlとname必要そう)
別軸
- FormProvider
- FormProviderで囲っておくと、useFormContextからregisterを取り出せる
Design and philosophy
- Introducing form state subscription model through the proxy
- Avoiding unnecessary computation
- Isolating component re-rendering when required
Path ≒ PathInternal
type Example = {
user: {
name: string;
address: {
street: string;
city: string;
};
};
// Going with 'Array' despite the original use of `<Array>` to avoid confusion
orders: Array<{
id: number;
amount: number;
}>
};
type PathInternal<Example> =
| 'user'
| 'user.name'
| 'user.address'
| 'user.address.street'
| 'user.address.city'
| 'orders'
| 'orders.id'
| 'orders.amount';