1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React Hook FormのuseFormに入門してみる

1
Posted at

React Hook FormのuseFormに入門してみる

まずuseFormは何か?

Reactでフォームを扱う時、「入力欄の状態管理」や「バリデーション」、「送信時の処理」などをどうするかが悩みどころです。その面倒な部分をまるっと助けてくれるのがreact-hook-formで、その中核にあるのがuseForm()です。

useFormを使うと、

  • 入力値の状態管理(useState書かなくてOK)
  • バリデーションチェック
  • データ送信
  • 初期値設定

などが一括で管理できます。

例で見るuseForm

import { useForm, SubmitHandler } from 'react-hook-form';

type FormValues = {
  name: string;
};

const MyForm = () => {
  const { register, handleSubmit } = useForm<FormValues>();

  const onSubmit: SubmitHandler<FormValues> = (data) => {
    console.log(data); // 例: { name: '入力された値' }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name')} />
      <button type="submit">送信</button>
    </form>
  );
};

useForm<FormValues>()のようにジェネリクスで型を指定することで、フォームの値に型安全性を持たせることができます。registerに渡す名前もFormValuesのキーに制限されるため、タイポ防止にもなります。

registerとは何か?

質問:「registerとhandleSubmitってなに?」

register('name')は、React Hook Formに「このinputタグを名前付きで管理してね」と伝える命令です。これをやることで、値が自動的にstateに格納されるようになります。つまり入力値が自動で管理されます。

handleSubmitとは何か?

これはフォーム送信時に呼び出される関数で、内部でバリデーションをしてくれます。バリデーションを通った場合だけ、onSubmitが呼ばれる仕組みです。

なんでスプレッド構文...register("name")を使うの?

疑問:「なんで...register()って書くのか。registerだけでいいんじゃないのか?」

実はregister('name')はこんなオブジェクトを返しています。

{
  onChange: ..., // 入力時の処理
  onBlur: ...,
  name: 'name',
  ref: ...
}

これを<input>に直接渡すには、...(スプレッド構文)で展開してあげる必要があります。つまり「必要なpropsをまとめて突っ込むため」に使います。

スプレッド構文を使用することで下のような感じでpropsをまとめて渡せます。

<input
  onChange={...}
  onBlur={...}
  name="name"
  ref={...}
/>

バリデーションを追加する

registerの第2引数にバリデーションルールを渡せます。エラーメッセージはformState.errorsから取り出せます。

import { useForm, SubmitHandler } from 'react-hook-form';

type FormValues = {
  name: string;
  age: number;
};

const MyForm = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>();

  const onSubmit: SubmitHandler<FormValues> = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        {...register('name', {
          required: '名前は必須です',
          minLength: { value: 2, message: '2文字以上入力してください' },
        })}
      />
      {errors.name && <p>{errors.name.message}</p>}

      <input
        type="number"
        {...register('age', {
          required: '年齢は必須です',
          min: { value: 0, message: '0以上で入力してください' },
        })}
      />
      {errors.age && <p>{errors.age.message}</p>}

      <button type="submit">送信</button>
    </form>
  );
};

バリデーションが通らない限りonSubmitは呼ばれないため、ハンドラ内で受け取るdataは「バリデーション済みの安全なデータ」と考えられます。

FormProviderとuseFormContextについて

FormProvideruseFormContextは、フォームを複数のコンポーネントに分けて使いたいときに便利な仕組みです。

コード例

import { useForm, FormProvider, useFormContext, SubmitHandler } from 'react-hook-form';

type FormValues = {
  email: string;
};

const InputField = () => {
  const methods = useFormContext<FormValues>(); // FormProviderから受け取れる!
  return <input {...methods.register('email')} />;
};

const MyForm = () => {
  const methods = useForm<FormValues>();

  const onSubmit: SubmitHandler<FormValues> = (data) => {
    console.log(data);
  };

  return (
    <FormProvider {...methods}>
      <form onSubmit={methods.handleSubmit(onSubmit)}>
        <InputField />
        <button type="submit">送信</button>
      </form>
    </FormProvider>
  );
};

そもそもmethodsって何か?

methodsっていうのは、useForm()から返される「フォーム管理用の関数や状態が詰まった道具箱」みたいなものです。

その中にはregisterhandleSubmitformStatesetValuegetValuesなどが入っています。

子コンポーネントに渡す必要がない場合は、const { register, handleSubmit } = useForm()のように分割代入で必要な関数だけ取り出すのが一般的です。FormProviderを使う時だけmethodsとしてまとめて受け取るイメージで使い分けるとスッキリします。

useFormContext()って何してる?いつ実行される?

useFormContext()は、FormProviderで共有されたmethodsを中から取り出して使うための関数です。これを子コンポーネントで呼び出すことで、propsでわざわざ渡さなくても、フォーム情報を扱えるようになります。

実行されるタイミングは、Reactの通常のレンダリングフローの中です。つまり、そのコンポーネントが描画されるときにuseFormContext()が呼ばれて、methodsが取り出されます。

useFormContext()useForm()の違いはなに?

  • useForm():新しいフォーム管理オブジェクト(methods)を生成する。主に親コンポーネントで使う
  • useFormContext():すでにFormProviderによって共有されたmethodsを取得する。子コンポーネントで使う

<FormProvider {...methods}>って具体的に何してる?

ReactのContext APIを使って、親コンポーネントで作成したmethods(箱)をコンポーネントツリー全体に共有しています。それにより、子コンポーネントではuseFormContext()を使うだけで同じmethodsにアクセスできるようになります。

関数/コンポーネント 役割
useForm() フォームの箱を作る
FormProvider その箱を「メンバー全員に共有」
useFormContext() その箱を「メンバーが開けて使えるようにする鍵」

補足:UIライブラリと組み合わせる時はController

MUIやChakra UIなどのUIライブラリを使う場合、registerがうまく動かないケースがあります(ライブラリ側がrefを独自に管理していたりするため)。

その場合はControllerコンポーネントを使うのが定石です。

import { useForm, Controller } from 'react-hook-form';
import { TextField } from '@mui/material';

type FormValues = {
  name: string;
};

const MyForm = () => {
  const { control, handleSubmit } = useForm<FormValues>();

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <Controller
        name="name"
        control={control}
        defaultValue=""
        render={({ field }) => <TextField {...field} label="名前" />}
      />
      <button type="submit">送信</button>
    </form>
  );
};

registerで動かないUIライブラリにはController」と覚えておくと迷いません。

次のステップ:Zodとの組み合わせ

ここまでのregister内バリデーションは便利ですが、実務ではzodと組み合わせるのがデファクトスタンダードになっています。

なぜZod?

  • スキーマからTypeScriptの型を自動生成できる(型と検証ロジックの二重管理が不要)
  • 複雑なバリデーション(条件付き、ネスト、配列など)が書きやすい
  • バックエンドと共通のスキーマを使い回せる

コード例

npm install zod @hookform/resolvers
import { useForm, SubmitHandler } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

// Zodスキーマを定義
const schema = z.object({
  name: z.string().min(2, '2文字以上入力してください'),
  age: z.number().min(0, '0以上で入力してください'),
});

// スキーマから型を自動生成
type FormValues = z.infer<typeof schema>;

const MyForm = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>({
    resolver: zodResolver(schema),
  });

  const onSubmit: SubmitHandler<FormValues> = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name')} />
      {errors.name && <p>{errors.name.message}</p>}

      <input type="number" {...register('age', { valueAsNumber: true })} />
      {errors.age && <p>{errors.age.message}</p>}

      <button type="submit">送信</button>
    </form>
  );
};

z.infer<typeof schema>によってスキーマから型が自動生成されるので、型とバリデーションが完全に同期するのが最大の強みです。

まとめ

React Hook Formは、Reactのフォーム管理を圧倒的に楽にしてくれるライブラリです。

  • useFormでフォームの状態管理を一括化
  • registerでinputを登録、handleSubmitで送信処理
  • FormProvider + useFormContextで子コンポーネントへ情報を共有
  • UIライブラリと組み合わせる時はController
  • 実務ではZod + zodResolverで型安全なフォームを実現

useStateで頑張ってフォームを書いている人は、ぜひ試してみてください!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?