10月29日、海外の新興技術ブログExpression Statementが「HTMLフォームバリデーションの活用不足について」と題した記事を公開した。この記事では、HTMLフォームバリデーションが備える強力な機能とその使用方法について詳しく紹介されている。以下に、その内容を紹介する。
バリデーション属性、メソッド、プロパティの概要
HTMLフォームの入力項目に対して、空の入力を許可しないためにはrequired属性を追加するだけで簡単に実現できる。
<input required>
さらに、以下のような手法で入力の制約を追加することができる。
- type属性の指定: email、number、urlなど
 - patternやmaxlengthのような追加の入力属性
 - DOMメソッドsetCustomValidityの使用(最も柔軟な方法)
 
setCustomValidity は特に強力で、任意の検証ロジックを作成し、複雑なケースにも対応可能だ。しかし、これは属性と異なりメソッド形式で実行する必要があり、使い勝手が悪いため、HTMLフォームバリデーションが十分に活用されていない一因とされている。
setCustomValidityの使い方
例えば以下のコードIにより、入力が無効な状態に設定できる。
input.setCustomValidity("入力が不正です");
空文字列を渡すことで入力を有効にできる。
input.setCustomValidity("");
例えば、required属性に相当する機能を手動で実装するには次のようにする。
<input
  name="example"
  placeholder="..."
  onChange={(event) => {
    const input = event.currentTarget;
    if (input.value === "") {
      input.setCustomValidity("入力は必須です");
    } else {
      input.setCustomValidity("");
    }
  }}
/>
初期レンダリングでのエラーメッセージの設定
コンポーネントが初期表示された際に空の入力が無効とされるようにするには、以下のコードを追加する。
import { useRef, useLayoutEffect } from "react";
function Form() {
  const ref = useRef();
  useLayoutEffect(() => {
    const input = ref.current;
    const empty = input.value !== "";
    input.setCustomValidity(empty ? "入力は必須です" : "");
  }, []);
  return (
    <form>
      <input
        ref={ref}
        name="example"
        onChange={(event) => {
          const input = event.currentTarget;
          if (input.value === "") {
            input.setCustomValidity("入力は必須です");
          } else {
            input.setCustomValidity("");
          }
        }}
      />
      <button>Submit</button>
    </form>
  );
}
このように初期設定で入力エラーを指定する方法には以下のような問題がある。
- onChangeハンドラと初期レンダリングのバリデーションロジックが重複している。
 - バリデーションロジックが分散し、コードの保守性が低下している。
 - 複数の入力項目があるフォームでは、冗長なコードが必要になる。
 
このようなボイラープレートの多さと使い勝手の悪さが、HTMLフォームバリデーションの利用が広がらない理由の一つだとされている。
提案されるAPIとcustom-validity属性の利用例
HTML仕様にはcustom-validity属性が存在しないが、ユーザーランドでこのような属性を実装することで、使いやすさが大幅に向上すると期待される。以下にその例を示す。
function Input({ customValidity, ...props }) {
  const ref = useRef();
  useLayoutEffect(() => {
    if (customValidity != null) {
      const input = ref.current;
      input.setCustomValidity(customValidity);
    }
  }, [customValidity]);
  return <input ref={ref} {...props} />;
}
function Form() {
  const [value, setValue] = useState("");
  const handleChange = (event) => setValue(event.target.value);
  return (
    <form>
      <Input
        name="example"
        value={value}
        onChange={handleChange}
        customValidity={value ? "" : "このフィールドを入力してください"}
      />
      <button>Submit</button>
    </form>
  );
}
このようなカスタム入力コンポーネントを利用することで、非同期検証ロジックなどの複雑なバリデーションも実現でき、将来的にこの属性が標準仕様に加えられることが期待される。
まとめ
setCustomValidityによるカスタムバリデーションは、多様な検証ニーズを満たす強力な機能であるが、その使い勝手が悪い点がHTMLフォーム検証の利用を阻んでいる。本記事で紹介されたcustom-validityのような柔軟で簡便な属性が標準に追加されれば、利用がさらに進むことが期待される。詳細はHTMLフォームバリデーションの活用不足についてを参照していただきたい。