🥌

React (typescript):yup に アプリ全体で使用できる独自の検証メソッドを追加する

2022/06/18に公開

React(typescript) で MUI + react-hook-form + yup で入力フォームを作成しています。
yup は独自の検証メソッドが追加できるのですが、独自の検証メソッドをアプリ全体で使用する方法がわかりにくかったので記事にしました。
https://github.com/jquense/yup#schemastripenabled-boolean--true-schema

yup に独自の検証メソッドを追加する

yup には required という必須検証用のメソッドがあります。
この required メソッドですが、string項目であれば、「undefined」と「空文字」の場合は検証エラーになります。

requiredメソッドは number 項目で使用すると「undefined」は検証エラーになりますが、「空文字」は Castエラー、「0」入力はもちろん検証エラーになりません。
値が undefinedの場合は必須エラーになる

値が空文字の場合は Castエラーになる

number 項目の場合、「undefined」と「空文字」と「0」は 必須検証でエラーにるようなカスタム検証を作成し、アプリ全体で使用できるようにしたいと思います。

検証エラーのメッセージを日本語化したい場合はコチラを参照ください。
React 今更だけど改めて Validation ライブラリ yup の日本語化

カスタム検証を定義するファイルを作成する

「undefined」、「空文字」、「0」を必須検証エラーにし、アプリ全体でyup.number().myRequired()と使用できる、myRequired カスタム検証 を作成していきます。

ファイル名は何でもいいですが、yup.custom.ts というファイルを作成しました。
yup をインポートして、カスタマイズしたうえでデフォルトエクスポートします。
このファイルに 検証エラーの日本語化や、今回のカスタム検証を定義します。

yup.custom.ts
import * as yup from 'yup'

// yupの検証エラーメッセージを日本語化するなら、日本語設定ファイルを指定する
// yup.setLocale(jpConfig)

// ここに拡張メソッドを追加します。


export default yup

作成した yup.custom.ts は App.ts などのルートコンポーネントで import しておきます。

App.ts
import "./styles.css";
// ルートコンポーネントで読み込んでおきます。
import "./yup.custom";

export default function App() {
・・・

カスタム検証を定義する

yup.number().myRequired()と使用できる myRequired メソッドは、
yup.number() メソッドが返す NumberSchema に追加するメソッドになります。

addMethodの型引数には、拡張メソッドを追加するスキーマー NumberSchema を指定します。
addMethodの
第一引数にはNumberSchemaを返すメソッド、yup.number を指定します。
第二引数は追加するメソッド名myRequiredを指定します。
第三引数は検証を行うメソッドを指定します。
アロー関数ではなく function を使うことで this が呼び出ししている検証中のobjectを参照できます。
検証内容は transform() で undefined と 空文字を 0 に変換し、notOneOf()で 0 の場合に検証エラーにしています。

yup.custom.ts
import * as yup from "yup";
import { RequiredNumberSchema } from "yup/lib/number";

// yup.number スキーマーに対して 'myRequired'メソッドを追加します。
yup.addMethod<yup.NumberSchema>(yup.number, "myRequired", function () {
  return this.transform((value, originalValue) => {
    return originalValue ? value : 0;
  }).notOneOf([0], "必ず入力してください。");
});

グローバルで使用できるようにする

アプリ全体でグローバルで使用できるようにするため、NumSchema インターフェースを拡張して、myRequired() を追加します。
myRequired()メソッドの戻り値は 'RequiredNumberSchema'を指定しています。
戻り値に'RequiredNumberSchema'を指定することによって、yup.number().myRequired()スキーマから推論される型は number 型 になります。
戻り値を'RequiredNumberSchema'ではなく'NumSchema'にすると、スキーマから推論される型は number とundefined のユニオン型 になります。

yup.custom.ts
import * as yup from "yup";
import { RequiredNumberSchema } from "yup/lib/number";
import { AnyObject, Maybe } from "yup/lib/types";
import { jpConfig } from "./yup.configJp";

// yup 日本語化
yup.setLocale(jpConfig);

yup.addMethod<yup.NumberSchema>(yup.number, "myRequired", function () {
  return this.transform((value, originalValue) => {
    return originalValue ? value : 0;
  }).notOneOf([0], "必ず選択してください。");
});

declare module "yup" {
  interface NumberSchema<
    TType extends Maybe<number> = number | undefined,
    TContext extends AnyObject = AnyObject,
    TOut extends TType = TType
  > extends yup.BaseSchema<TType, TContext, TOut> {
+    myRequired(): RequiredNumberSchema<TType, TContext>;
  }
}

export default yup;

カスタム検証を使う

Demo コンポーネントで、作成したカスタム検証を使ってみます。
今回作成した yup.custom.ts を インポートして、yup はこのファイル経由で使用します。
あとは yup.number().myRequired()と使用するだけです。

Demo.tsx
// カスタムyupをimportする
import yup from "./yup.custom";

// yupスキーマオブジェクト
const InputSchema = yup.object({
  firstName: yup.string().required(),
  age: yup.number().required(),
+  customage: yup.number().myRequired()
});

// yup スキーマから 型を推論する
export type InputType = yup.InferType<typeof InputSchema>;
// firstName: string
// age: number
// customage: number

export const Demo = () => {
・・・

サンプル

今回のコード全体はコチラ
サンプルは React(typescript) + MUI + react-hook-form で作成しています。

Discussion