EY-Office ブログ

HTML5 Form Validationのことを思い出したのでMUIで使ってみた

React + MUI(Material-UI)を使って簡単なフォームを作っていたのですが、さあバリデーション(Validation)はどうしようかな?と思い、ネットを調べだしました。
そもそもReactを使いフォームを作る際にはReact Hook Formが定番ですが、MUIと組み合わせるにはControllerを使う必要があり、複雑度が上がるので(使わない方法もあるらしいです)今回はReact Hook Formの採用は見送りました。

さてさて、バリデーション(Validation)に調べると10 Best JavaScript Validation Libraries in 2022 Openbaseなどたくさんの記事があります、日本語でもReactで使えるバリデーションライブラリを紹介!などがあり、どうしようか迷っていました。

HTML5 Form Validation

HTML5 Form Validation

私はPerlやJavaの時代から長らくWebアプリを作っているので「Validationはプログラムで行う事」という常識がありました。しかし、ネットを調べていてHTML5で追加されたクライアント側の検証(Client-side validation)を発見しました!😅

HTML5ではブラウザーにバリデーションの機能が組み込まれています。詳しくはクライアント側のフォームデータ検証(Client-side form validation)に書かれていますが、

  • 必須項目の指定、inputタグ等のrequired属性
  • 文字数や値の下限・上限の指定、inputタグ等のmin,maxlength属性など
  • 正規表現での文字列パターンの指定、inputタグ等ののpattern属性
  • エラーメッセージ
  • エラーの表示(CSS)機能
  • JavaScript用API、inputタグ等のvalidityプロパティなど
  • JavaScriptを使った独自チェック、setCustomValidity()メソッド

などがサポートされています。簡単なJavaScriptを組み合わせるだけでバリデーションが実現できるのです。

MUIのHTML5 Form Validationサポート

MUIの<TextField>等はinputProps属性で、required, min, patternなどのHTML5 Form Validation用属性を指定できます。また、カッコ良くエラーメッセージを表示するための

  • errorプロパティー(bool)、エラーを示すハイライト表示設定
  • helperText(node, string)、エラーメッセージの設定

があります。これを組み合わせると、HTML5 Form Validationが使えます。

サンプルコード

実際のコードはRedux Toolkitを使っていますが、ここでは下図のようなuseStateを使った説明用のコードを作りました。

HTML5 Form Validation

  • ① メールアドレスチェック用正規表現、クライアント側の検証から持ってきました
  • ② inputタグのDOM属性をアクセスするためのuseRef
  • ③ 入力値を保持するstate
  • ④ エラー有無を保持するstate
  • ⑤ 全フォームの検証を行う関数
  • ⑥ Email入力input(TextField)のrefが設定されているか
  • ⑦ Email入力inputにHTML5バリデーション結果の取得
  • ⑧ バリデーション結果(エラー状態)をstateへ設定
  • ⑨ 全フォームが正しければvalidがtrueにする
  • ⑩ パスワード入力input(TextField)のHTML5バリデーションのコード
  • ⑪ パスワード確認入力input(TextField)のHTML5バリデーションのコード
  • ⑫ パスワード入力、パスワード確認入力が等しくなければ、独自エラーsetCustomValidityを設定
  • ⑬ Email等と同様のHTML5バリデーションのチェック
  • ⑭ 全フォームが正しいかを戻す
import { Button, Container, TextField } from "@mui/material";
import React, { useRef, useState } from "react";

type OnChangeEvent = React.ChangeEvent<HTMLInputElement>;
const EmailVaildPattern =                                           // ← ①
  "^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:.[a-zA-Z0-9-]+)*$";

const App = () => {
  const emailRef = useRef<HTMLInputElement>(null);                  // ← ②
  const passwordRef = useRef<HTMLInputElement>(null);
  const confirmPasswordRef = useRef<HTMLInputElement>(null);
  const [emailValue, setEmailValue] = useState("");                 // ← ③
  const [passwordValue, setPasswordValue] = useState("");
  const [confirmPasswordValue, setConfirmPasswordValue] = useState("");
  const [emailError, setEmailError] = useState(false);              // ← ④
  const [passwordError, setPasswordError] = useState(false);
  const [confirmPasswordError, setConfirmPasswordError] = useState(false);

  const formValidation = (): boolean => {                          // ← ⑤
    let valid = true;

    const e = emailRef?.current;                                   // ← ⑥
    if (e) {
      const ok = e.validity.valid;                                 // ← ⑦
      setEmailError(!ok);                                          // ← ⑧
      valid &&= ok;                                                // ← ⑨
    }
    const p = passwordRef?.current;                                // ← ⑩
    if (p) {
      const ok = p.validity.valid;
      setPasswordError(!ok);
      valid &&= ok;
    }
    const c = confirmPasswordRef?.current;                         // ← ⑪
    if (c) {
      if (confirmPasswordValue.length > 0 &&                       // ← ⑫
          passwordValue !== confirmPasswordValue) {
        c.setCustomValidity("パスワードが一致しません");
      } else {
        c.setCustomValidity("");
      }

      const ok = c.validity.valid;                                // ← ⑬
      setConfirmPasswordError(!ok);
      valid &&= ok;
    }

    return valid;                                                // ← ⑭
  };

  return (
    <Container component="main" maxWidth="xs">
      <TextField
        margin="normal"
        fullWidth
        required
        inputRef={emailRef}                                             // ← ⑮
        value={emailValue}                                              // ← ⑯
        error={emailError}                                              // ← ⑰
        helperText={emailError && emailRef?.current?.validationMessage} // ← ⑱
        inputProps={ {required: true, pattern: EmailVaildPattern} }     // ← ⑲
        onChange={(e: OnChangeEvent) => setEmailValue(e.target.value)}  // ← ⑳
        label="Email"
      />

      <TextField
        margin="normal"
        fullWidth
        required
        type="password"
        inputRef={passwordRef}
        value={passwordValue}
        error={passwordError}
        helperText={passwordError && passwordRef?.current?.validationMessage}
        inputProps={ {required: true} }
        onChange={(e: OnChangeEvent) => setPasswordValue(e.target.value)}
        label="Password"
      />

      <TextField
        margin="normal"
        fullWidth
        required
        type="password"
        inputRef={confirmPasswordRef}
        value={confirmPasswordValue}
        error={confirmPasswordError}
        helperText={confirmPasswordError &&
                    confirmPasswordRef?.current?.validationMessage}
        inputProps={ {required: true} }
        onChange={(e: OnChangeEvent) => setConfirmPasswordValue(e.target.value)}
        label="Confirm password"
      />

      <Button
        variant="contained"
        fullWidth
        sx={ {mt: 3} }
        onClick={() => {              // ← ㉑
          if (formValidation()) {
            alert("OK!");
          }
        }}
      >
        Register
      </Button>
    </Container>
  );
};

export default App;
  • TextFieldの使うinputタグのrefを指定
  • ⑯ 入力値の設定
  • ⑰ エラー状態の設定、エラーなら入力が赤色になる
  • ⑱ エラーメッセージの指定、inputタグからvalidationMessageを取得
  • ⑲ HTML5バリデーションの指定、ここでは必須項目, Emailのパターン を指定
  • ⑳ キー入力された文字をstateに設定
  • Registerボタンを押したさいにバリデーションが実行され、エラーがなければアラートのOKが表示されます

まとめ

多数の入力があり複雑なバリデーションを行う場合や、HTML5未対応のブラウザーに対応するには10 Best JavaScript Validation Libraries in 2022 OpenbaseReactで使えるバリデーションライブラリを紹介! にあるようなライブラリーを使う事を検討した方が良いかと思いますが、シンプルなバリデーションだけならHTML5 Form Validationで充分かなと思いました。

- about -

EY-Office代表取締役
・プログラマー
吉田裕美の
開発者向けブログ