LoginSignup
0
1

[React] Formの取り扱いについて理解する

Last updated at Posted at 2024-05-17

Reactでのフォームの取り扱い

Reactでのフォームの取り扱いについて、要点をまとめました。


フォームデータの取り扱い

Reactでのフォームデータの取扱いは、大きく分けて2パターンある。

制御コンポーネント

フォームの値を親コンポーネントでstateとして管理し、値の反映には stateのリフトアップ を用いる。
Reactは、単方向データフロー を採用しているため、基本的にこちらの方法を推奨。

  • メリット
    • 常に値にアクセスできるので、ユーザーの入力中にバリデーションが可能
  • デメリット
    • 入力値が更新されるたびにコンポーネントが再レンダリングされる

非制御コンポーネント

仮想DOMを経由せず直接DOM要素の値にアクセスする手法
useRefを使用する

  • メリット
    • 入力値が更新されるたびにコンポーネントが再レンダリングされないので、パフォーマンス効率が良い
  • デメリット
    • ユーザーの入力中にバリデーションをすることが難しい

イベントパラメータについて

Reactでは、コンポーネントでイベントを扱う際、Eventオブジェクトではなく、React独自の Synthetic Event を用いる。

イベントパラメータを引数に受け取る場合は、SyntheticEventを継承した、特定のイベント型を指定する。

:pencil: イベントハンドラと対応するデータ型

type Props = {
  onClick: (event: React.MouseEvent<HTMLInputElement>) => void
  onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
  onkeypress: (event: React.KeyboardEvent<HTMLInputElement>) => void
  onBlur: (event: React.FocusEvent<HTMLInputElement>) => void
  onFocus: (event: React.FocusEvent<HTMLInputElement>) => void
  onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
  onCancel: (event: React.React.SyntheticEvent<HTMLDialogElement>) => void
}

:pencil: 利用例

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setFormData({ ...formData, inputValue: event.target.value });
};
// ...
<input type="text" onChange={handleChange} />

:pencil: [サンプルコード] 制御コンポーネント

  • フォーム全体のデータをformDataとしてstate管理
  • フォームデータを更新するためのコールバック関数を定義し、子コンポーネントのイベントとして設定
    => Stateのリフトアップ
import React, { useState } from 'react';

type FormData = {
  firstName: string;
  lastName: string;
};

const ControlledComponent: React.FC = () => {
  const [formData, setFormData] = useState<FormData>({
    firstName: '',
    lastName: '',
  });

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setFormData((prevData) => ({
      ...prevData,
      [name]: value,
    }));
  };

  return (
    <form>
      <input
        type="text"
        name="firstName"
        value={formData.firstName}
        onChange={handleChange}
      />
      <input
        type="text"
        name="lastName"
        value={formData.lastName}
        onChange={handleChange}
      />
    </form>
  );
};

:pencil: [サンプルコード] 非制御コンポーネント

  • useRefを使用して、コンポーネントの参照を保持(仮想DOMを介さない)
  • 値の取得 / 更新時はDOM要素を直接参照 / 更新する
import React, { useRef } from 'react';

const UncontrolledComponent: React.FC = () => {
  const firstNameRef = useRef<HTMLInputElement>(null);
  const lastNameRef = useRef<HTMLInputElement>(null);

  return (
    <form>
      <input type="text" name="firstName" ref={firstNameRef} />
      <input type="text" name="lastName" ref={lastNameRef} />
    </form>
  );
};

React Hook Form

React Hook Formとは?

フォームヘルパーの1つ。
複雑なフォームを扱う場合などに用いられる。
執筆時 npm trendでのダウンロードが1番多かったので採用

image.png

React Hook Formの特徴

非制御コンポーネントを用いて、フォームの入力データを管理する

サンプルコード

React Hook Formを利用するまでの基本的な流れです。
1.ㅤuseFormをインポート
2.ㅤuseFormの引数にオプションを指定、戻り値で利用する機能をオブジェクトプロパティとして受け取る

const { register, handleSubmit, reset } = useForm<FormData>({
    defaultValues: { username: '', isAgreed: false, },
});

3.ㅤregister関数でフォーム要素を登録する

<input  {...register('username')} />
  • 引数で指定した値(今回の場合はusername)がセットされた、name属性が付与
  • 戻り値にrefプロパティが含まれるため、非制御コンポーネントによるDOM管理が実施

4.ㅤhandleSubmitでのサブミット処理のハンドリング

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

//...
<form action="#" onSubmit={handleSubmit(onSubmit)}>

[:pencil: 応用] バリデーション

react-hook-formにはバリデーションをサポートするカスタムリゾルバが複数ある。

カスタムリゾルバとは?

フォームバリデーションをカスタマイズするために使用。
独自のバリデーションロジックを実装したい場合に用いられる。

yup

カスタムリゾルバの1つ
旬が過ぎたかもしれないが...yupの記事が多かったのでyupを使用します。

image.png

サンプルコード

yupを利用するまでの基本的な流れです。

1.ㅤsrc/schema にフォームデータのスキーマを定義したファイルの作成

src/schema/userForm.ts

import * as yup from 'yup';
import type { InferType } from 'yup';
import { sex, userRole } from '@/constants/user_form_constants';

export const userForm = yup.object({
  name: yup.string().required('名前の入力は必須です'),
  email: yup.string().required('メールアドレスの入力は必須です'),
  sex: yup.mixed().oneOf(Object.keys(sex)),
  role: yup.mixed().oneOf(Object.keys(userRole)),
  agreement: yup.boolean().oneOf([true], '利用規約の同意が必要です').required(),
});

export type UserFormSchema = InferType<typeof userForm>;

2.ㅤFormコンポーネントにインポート

import { yupResolver } from '@hookform/resolvers/yup';
import type { UserFormSchema } from '@/schema/userForm';
import { userForm } from '@/schema/userForm';

3.ㅤuseFormの変更

  • ジェネリックにインポートしたスキーマのデータ型を指定
  • 第二引数にyupResolverを指定
  • 戻り値からformState: { errors } の取得
const {
    register,
    handleSubmit,
    reset,
    formState: { errors },
  } = useForm<UserFormSchema>({
    defaultValues: {
      name: '',
      email: '',
      sex: 'man',
      role: 'readonly',
      agreement: false,
    },
    resolver: yupResolver(userForm),
  });

4.ㅤエラーメッセージの表示

  • {errors.username?.message}

最終的なコード

import { yupResolver } from '@hookform/resolvers/yup';
import { SubmitHandler, useForm } from 'react-hook-form';

import type { UserFormSchema } from '@/schema/userForm';

import { sex, userRole } from '@/constants/user_form_constants';
import { userForm } from '@/schema/userForm';

const SampleReactHookYupResolver = () => {
  const {
    register,
    handleSubmit,
    reset,
    formState: { errors },
  } = useForm<UserFormSchema>({
    defaultValues: {
      name: '',
      email: '',
      sex: 'man',
      role: 'readonly',
      agreement: false,
    },
    resolver: yupResolver(userForm),
  });

  const onReset = (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault();
    reset();
  };

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

  return (
    <>
      <form action="#" onSubmit={handleSubmit(onSubmit)}>
        <div>
          <label htmlFor="name">氏名</label>
          <input id="name" {...register('name')} />
          <div>{errors.name?.message}</div>
        </div>
        <div>
          <label htmlFor="email">メールアドレス</label>
          <input id="email" {...register('email')} />
          <div>{errors.email?.message}</div>
        </div>
        <div>
          <label htmlFor="sex">性別</label>
          <select {...register('sex')}>
            {Object.entries(sex).map(([key, value], index) => (
              <option key={index} value={key}>
                {value}
              </option>
            ))}
          </select>
          <div>{errors.sex?.message}</div>
        </div>
        <div>
          <label htmlFor="role">ロール</label>
          <select id="role" {...register('role')}>
            {Object.entries(userRole).map(([key, value], index) => (
              <option key={index} value={key}>
                {value}
              </option>
            ))}
          </select>
          <div>{errors.role?.message}</div>
        </div>
        <div>
          <label htmlFor="agreement">利用規約</label>
          <input id="agreement" type="checkbox" {...register('agreement')} />
          <div>{errors.agreement?.message}</div>
        </div>
        <div>
          <button>Submit</button>
        </div>
        <div>
          <button onClick={onReset}>Reset</button>
        </div>
      </form>
    </>
  );
};

export default SampleReactHookYupResolver;

まとめ

以上です。
覚えること多いな! って印象でした。
ユーザビリティが高いフォームを作っている皆さんに感謝😇

0
1
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
0
1