🥌

React yup の cast エラー を いい感じで処理する

2022/05/16に公開

React + Typescript + react-hook-form + yup で環境構築しています。

数値項目を入力するフィールドに、数値以外を入力したときに yup で表示される castエラーや、日付項目を入力するフィールドに、日付以外を入力したときに yup で表示される castエラーを、いい感じで処理する tips です。

サンプルは数値入力項目で書いていますが、日付項目などでも同じです。

yup の導入の仕方などについてはコチラ
【2022年】 React Hook FormでValidationライブラリはどれにするか?

標準の動作

たとえば数値項目に数値以外のものを入力すると yup は以下のように
「must be a number type, but the final value was: NaN (cast from the value "").」
と表示します。

export const createSchema = yup.object({
  numberField: yup.number().label('数値入力項目')
})

数値以外の入力は 0 として処理する

数値以外の値を入力された場合はエラーにせず 0 として処理するには、transformメソッドを使用して 0 に変換します。

export const createSchema = yup.object({
  numberField: yup.number().label('数値入力項目')
      .transform((value, originalvalue) => (originalvalue === '' ? 0 : value))
})

transformメソッドで 0 に変換した後、最小値や最大値の検証メソッドを繋げます。

export const createSchema = yup.object({
  numberField: yup.number().label('数値入力項目')
      .transform((value, originalvalue) => (originalvalue === '' ? 0 : value))
      .min(-999).max(999)
})

数値以外の入力はエラーにして日本語のメッセージにする

数値以外の値を入力された場合、日本語のエラーを表示するには typeErrorメソッドで日本語メッセージを指定します。

export const createSchema = yup.object({
  numberField: yup.number().label('数値入力項目')
      .typeError('数値を入力してください。')
      .min(-999).max(999)
})

yup の configファイル で日本語化する

以前、こちらの記事で yup のエラーメッセージを日本語化しました。
React 今更だけど改めて Validation ライブラリ yup の日本語化

yup 日本語化の為の config ファイルを再掲します。
typeError は mixedプロパティの notType プロパティにあたります。

yup.locale.ts
import * as yup from 'yup'
import { MessageParams } from "yup/lib/types"

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

const jpConfig = {
  mixed: {
    default: (prm: MessageParams) => `${labelText(prm)}無効です`,
    required: (prm: MessageParams) => `${labelText(prm)}必須の入力項目です`,
    oneOf: (prm: MessageParams & {values: any}) => `${labelText(prm)}次の値のいずれかでなければなりません:${prm.values}`,
    notOneOf: (prm: MessageParams & {values: any}) => `${labelText(prm)}次の値のいずれかであってはなりません:${prm.values}`,
+    notType: `形式が違います`,
    defined: ``
  },
  string: {
    length: (prm: MessageParams & {length: number}) => `${labelText(prm)}${prm.length}文字でなければなりません`,
    min: (prm: MessageParams & {min: number}) => `${labelText(prm)}少なくとも${prm.min}文字でなければなりません`,
    max: (prm: MessageParams & {max: number}) => `${labelText(prm)}最大${prm.max}文字でなければなりません`,
    matches: (prm: MessageParams & {regex: RegExp}) => `${labelText(prm)}次の形式と一致する必要があります: "${prm.regex}"`,
    email: (prm: MessageParams & {regex: RegExp}) => `${labelText(prm)}メールアドレス形式である必要があります`,
    url: (prm: MessageParams & {regex: RegExp}) => `${labelText(prm)}有効なURLでなければなりません`,
    uuid: (prm: MessageParams & {regex: RegExp}) => `${labelText(prm)}有効なUUIDでなければなりません`,
    trim: (prm: MessageParams) => `${labelText(prm)}前後にスペースを入れてはいけません`,
    lowercase: (prm: MessageParams) => `${labelText(prm)}小文字でなければなりません`,
    uppercase: (prm: MessageParams) => `${labelText(prm)}大文字でなければなりません`,
  },
  number: {
    min: (prm: MessageParams & {min: number}) => `${labelText(prm)}${prm.min}以上である必要があります`,
    max: (prm: MessageParams & {max: number}) => `${labelText(prm)}${prm.max}以下でなければなりません`,
    lessThan: (prm: MessageParams & {less: number}) => `${labelText(prm)}${prm.less}より小さくなければなりません`,
    moreThan: (prm: MessageParams & {more: number}) => `${labelText(prm)}${prm.more}より大きくなければなりません`,
    positive: (prm: MessageParams & {more: number}) => `${labelText(prm)}正の数でなければなりません`,
    negative: (prm: MessageParams & {less: number}) => `${labelText(prm)}負の数でなければなりません`,
    integer: (prm: MessageParams) => `${labelText(prm)}整数でなければなりません`,
  },
  date: {
    min: (prm: MessageParams & {min: Date | string}) =>  `${labelText(prm)}${prm.min}より後でなければなりません`,
    max: (prm: MessageParams & {max: Date | string}) =>  `${labelText(prm)}${prm.max}より前でなければなりません`,
  },
  boolean: {
    isValue: (prm: MessageParams) =>  `${labelText(prm)}値が必要です`,
  },
  object: {
    noUnknown: (prm: MessageParams) => `${labelText(prm)}オブジェクトシェイプで指定されていないキーを含めることはできません`,
  },
  array: {
    length: (prm: MessageParams & {length: number}) => `${labelText(prm)}${prm.length}個が必要です`,
    min: (prm: MessageParams & {min: number}) => `${labelText(prm)}${prm.min}個以上の項目が必要です`,
    max: (prm: MessageParams & {max: number}) => `${labelText(prm)}${prm.max}個以下の項目が必要です`,
  },
}

yup.setLocale(jpConfig)
export default yup

yup のスキーマ定義を変更して実行してみます。

export const createSchema = yup.object({
  numberField: yup.number().label('数値入力項目')
      .min(-999).max(999)
})

config に定義されているメッセージが表示されています。

config の内容を変更して、もうちょっといい感じにしてみます。
数値項目なら「xxxxxは数値を入力してください。」日付項目なら「xxxxxは日付を入力してください。」と表示するようにconfigファイルの内容を変更します。

yup.locale.ts の一部
mixed: {
    default: (prm: MessageParams) => `${labelText(prm)}無効です`,
    required: (prm: MessageParams) => `${labelText(prm)}必ず入力してください。`,
    oneOf: (prm: MessageParams & {values: any}) => `${labelText(prm)}次の値のいずれかでなければなりません:${prm.values}`,
    notOneOf: (prm: MessageParams & {values: any}) => `${labelText(prm)}次の値のいずれかであってはなりません:${prm.values}`,
-   notType: `形式が違います`,
+    notType: (prm: MessageParams) => {
+      if (prm.type === 'number') {
+        return `${labelText(prm)}数値を入力してください。`
+      } 
+      if (prm.type === 'date') {
+        return `${labelText(prm)}日付を入力してください。`
+      }
+      return `${labelText(prm)}正しい形式を入力してください。`
+    } ,
    defined: ``
  },

わかってしまえばなんてこともない処理なのですが typeError == mixed.notType に気付けなくて思わず時間喰ってしまった処理でした。

Discussion