💡

React Form HookとYupを使って、validationとstate管理を楽にする

2024/02/16に公開

はじめに

SREホールディングス株式会社にて、ソフトウェアエンジニアをやっております、叶です。
今回はフロントエンド(React)のライブラリreact-form-hook yup 組み合わせて使うことによって、画面のvalidationとstate管理を楽にする方法を紹介いたします。

対象読者

  • Reactを用いて、フォーム開発を楽にしたい方

使用したライブラリ(React)

  • react-form-hook(7.49.3): フォームを管理するライブラリです。
  • yup(1.2.0): Jason Quense氏が開発している、バリデーションのためのライブラリです。

Yup

Yupとは

フォームの入力値を解析してバリデーションを行うために、JavaScriptでスキーマ(データ構造)を定義するためのライブラリです。
https://github.com/jquense/yup

Yupを用いて、やったこと

  1. フォームのスキーマを定義し、スキーマからフォームタイプに変換する
  2. 項目ごとにvalidationを定義する
  3. ラベル名を定義する
  4. メッセージを定義する

React Form Hook

React Form Hookとは

Reactでフォームを簡単に扱うことのできるライブラリです。
※フォームのvalidationの定義もできますが、今回はReact Form Hookの代わりにYupを使います。
https://react-hook-form.com/

React Form Hookを用いて、やったこと

  1. フォームの項目のstateをReact Form Hookで管理できる(いちいちuseStateを使わなくても済む)
  2. isDirty(変更ありなし)isValid(入力値が正しいか否か)などを使って、フォーム全体または個別のフィールドのstateを確認することができる

ハンズオン

Yupでメッセージの定義

Yupのデフォルトエラーメッセージはエラーになった項目の英語名で表示するので、開発者じゃないとわからないですが、定義したラベルを使用して、メッセージを設定し直すことにより、共通でわかりやすいメッセージを定義することができます。

// メッセージパラメータのタイプを定義
interface MessageParams {
  path: string;
  value: any;
  originalValue: any;
  label: string; // スキーマに定義したlabelの値がこちらに設定される
  type: string;
}

const labelText = (prm: MessageParams) => {
  return prm.label ? `${prm.label}` : "";
};

const jpConfig = {
  mixed: {
    default: (prm: MessageParams) => `${labelText(prm)}無効です。`, // デフォルトエラーメッセージ
    required: (prm: MessageParams) => `${labelText(prm)}必須です。`, // 必須エラーメッセージ
    notType: (prm: MessageParams) => {
      if (prm.type === "number") {
        // 数字タイプエラーメッセージ
        return `${labelText(prm)}数字でご入力ください。`;
      } else {
        return `${labelText(prm)}無効です。`;
      }
    }
  }
};

yup.setLocale(jpConfig);

↓メッセージ定義で参考した記事↓
https://zenn.dev/longbridge/articles/04f40422501348

スキーマ及びラベルを定義

const userSchema = yup.object({
  userName: yup.string().label("ユーザー").required(),
  email: yup.string().label("メールアドレス").required(),
  age: yup.number().label("年齢").required()
});

スキーマからタイプに変換

type UserType = yup.InferType<typeof userSchema>;

React Form Hookを定義

  • 返却した項目について
    • reset: デフォルト値をリセットか、フォームの項目をデフォルトにリセットする
    • handleSubmit: サブミットメソッドを登録する
    • register: フォーム項目を登録する
    • getValues: フォームの値を取得する
    • formState: フォームのステート(バリデーション結果 変更有無 デフォルト値 エラーメッセージなど)を取得する
  • mode: バリデーションを実行するタイミングをフォーカスアウトにする
  • yupResolverライブラリを使って、react-form-hookのresolverに設定することで、定義したvalidationが動作する
const {
    reset,
    handleSubmit,
    register,
    getValues,
    formState: { isValid, isDirty, defaultValues, errors }
} = useForm<UserType>({
    mode: "onBlur",
    resolver: yupResolver<UserType>(userSchema)
});

イベントを定義

  • サブミット
    • resetに値を入れると、フォームのデフォルト値が再設定される
const submit = () => {
  reset(getValues());
};
<!-- 省略 -->

<Button
  onClick={handleSubmit(submit)}
  disabled={!(isValid && isDirty)}
>登録</Button>
  • リセット
    • resetを実行すると、フォームの項目を設定したデフォルト値にリセットされる
const resetUser = () => {
  reset();
};
<!-- 省略 -->

<Button onClick={resetUser}>リセット</Button>

フォームを作成

  • yupで定義したフィールドを利用して、フォームを作成する
{Object.entries(userSchema.fields).map(([key, field]) => (
  <Stack
    direction="column"
    gap={2}
    alignItems="left"
    justifyContent="flex-start"
    width="300px"
  >
    <!-- 表示するラベルはスキーマに定義したラベルを利用する -->
    <Typography>{field.spec.label}</Typography>
    <TextField
       {...register(key)}
       error={!!errors[key]}
       helperText={(errors && errors[key]?.message) ?? ""}
       fullWidth
    />
  </Stack>
))}

Codepenでお試し

https://codepen.io/kdozdfdg-the-styleful/pen/BabpojJ

まとめ

今まで、フォームの項目が多い際に、悩んでいた問題がReact Form HookYupを使って、解決できて、楽になりましたね。

  • useStateは面倒
    • React Form Hookで管理にした
  • 必須チェックのメッセージは項目ごとに定義しないといけない
    • Yupで共通的なエラーのメッセージを自定義することで、いちいち書かなくても済む
  • 項目定義を探すのは面倒
    • 項目属性(項目日本語名を含む)をYupで統一なところに定義することによって、一目瞭然になった
SRE Holdings 株式会社

Discussion