Next.js PR

【なるべく簡単に】Next.js(React)でinputのバリデーション

記事内に商品プロモーションを含む場合があります

今、Webアプリを作っているのですが、
その実装中にinputタグのバリデーションではまったので、
採用した方法について書きたいと思います。

調べてみた限り、
react-hook-formが主流?みたいですが、
あんまり実装したくなかったので、他の方法を探しました。

構成

フォルダ

  • app
    • _services
    • components
      • InputText.tsx
    • page.tsx
{
  "name": "paleo-web",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "test": "jest"
  },
  "dependencies": {
    "@types/node": "20.3.1",
    "@types/react": "18.2.14",
    "@types/react-dom": "18.2.6",
    "autoprefixer": "10.4.14",
    "eslint": "8.43.0",
    "eslint-config-next": "13.4.7",
    "next": "13.4.7",
    "postcss": "8.4.24",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "tailwindcss": "3.3.2",
    "typescript": "5.1.3"
  },
  "devDependencies": {
    "@testing-library/jest-dom": "^6.1.3",
    "@testing-library/react": "^14.0.0",
    "@testing-library/user-event": "^14.5.1",
    "@types/jest": "^29.5.5",
    "jest": "^29.7.0",
    "jest-environment-jsdom": "^29.7.0"
  }
}

inputタグ(InputText.tsx)

親コンポーネントからバリデーションのパターンと
そのパターン検証に失敗した場合のエラーメッセージを渡しています。
以下は数字に限定しています。

<InputText id="age" label="年齢" value={age} placeholder="" 
  validationPattern="[0-9]*" validationErrorMessage="数値で入力してください" 
  onChange={onChangeAge}></InputText>

CSSはTailwind CSSですが、そこでpeerを使います。
詳細はここにも書いていますが、inputのclassNameにpeerを設定して、
peer-invalid:visibleをエラーメッセージ表示のUI(今回の場合はspan)に設定します。
これだけでpatternに設定した検証に失敗したらspanのメッセージが表示されます。

        <div className="flex flex-col">
            <label htmlFor={props.id} className="text-sm font-bold text-gray-700 tracking-wide">
                {props.label}
            </label>
            <input
                id={props.id}
                type="text"
                placeholder={props.placeholder}
                className="px-4 py-2 mt-2 text-base text-gray-700 placeholder-gray-600 border rounded-lg focus:outline-none focus:shadow-outline peer"
                value={props.value}
                pattern={props.validationPattern}
                onChange={props.onChange}
            />
            <span className="text-red-500 text-xs invisible peer-invalid:visible">{props.validationErrorMessage}</span>
        </div>

 <input
                id={props.id}
                type="text"
                placeholder={props.placeholder}
                className="px-4 py-2 mt-2 text-base text-gray-700 placeholder-gray-600 border rounded-lg focus:outline-none focus:shadow-outline peer"
                value={props.value}
                pattern={props.validationPattern}
                onChange={props.onChange}
 />
<span className="text-red-500 text-xs invisible peer-invalid:visible">{props.validationErrorMessage}</span>

親コンポーネト側(page.tsx)

親コンポーネント(page.tsx)では
OnChangeイベントで、inputのイベントがくるので、
それの!event.target.validity.validでinputがエラー状態か見れるので、
それを使ってます。親で色々実装したくなかったので、この方法にしました。
※子供でpatternでバリデーションして、親でもバリデーションのコードを書くのが嫌だった

ValidityStateはブラウザのバージョンによっては使えないかもしれません。
私のアプリは使えるブラウザしかサポートしません笑

const [buttonDisabledAge, setButtonDisabledAge] = useState(true);

    const onChangeAge = (event: React.ChangeEvent<HTMLInputElement>): void => {
        setButtonDisabledAge(!event.target.validity.valid);
        setAge(event.target.value);
    }

あとはbuttonDisabledAgeの値によって、ボタンの表示・非表示が切り替わります。

                <button disabled={buttonDisabledAge} className="rounded-full disabled:bg-gray-500 bg-blue-500 p-4 text-white hover:bg-blue-700 hover:shadow-xl peer-invalid:disabled" 
                  onClick={handleClick}>計算する</button>

以上、誰かの参考になれば!