Open9

react-hook-form

kzk4043kzk4043

get started

https://www.youtube.com/watch?v=RkXv4AXXC_4

動画わかりやすい

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>
  )
}
kzk4043kzk4043

One of the key concepts in React Hook Form is to register your component into the hook

kzk4043kzk4043

コンポ化した場合

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するパターン?

kzk4043kzk4043
  • registerパターン
    • 名前付き(register実行後の結果を渡す)
      • 親でregisterを実行して渡す
      • refが生成されている
    • 名前なし(register実行前の関数を渡す)
      • 型を渡す必要あり
      • 子でregisterを実行する
      • refはない(その対象DOMで実行するから)
  • 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を取り出せる
kzk4043kzk4043

Design and philosophy

  • Introducing form state subscription model through the proxy
  • Avoiding unnecessary computation
  • Isolating component re-rendering when required
kzk4043kzk4043

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';