React のアクセシビリティ改善に役立つユニークIDの生成

はじめに

【全部俺】 Web フロントエンドエンジニアのメモ Advent Calendar 2022 4 日目の記事です。
React 18 に追加された新しいフック useId を活用することで、重複やハイドレーションエラーを起こさずにユニークな ID を生成する方法を解説します。
(React 19 を使用しています)

ユニーク ID の必要性

アクセシビリティを改善するために HTML Attribute へ ID を指定することがよくあります。
  • 固定値を設定した場合、同じコンポーネントが複数レンダリングされた場合 ID が重複してしまいます。
  • 乱数にした場合、生成された HTML とハイドレーション時の ID が異なることにより整合性が取れないためパフォーマンスの問題が発生してしまいます。

useId について

これらの問題を解決するために、React 18 で新しいフック useId が登場しました。
フック API リファレンス – React
https://ja.reactjs.org/docs/hooks-reference.html#useid
useId はハイドレーション時の不整合を防ぎつつサーバとクライアント間で安定な一意 ID を作成するためのフックです。

使用方法

公式ドキュメントによると、1 つのコンポーネントで複数の ID を使う場合、接尾辞を付けるよう記載がありました。
  • label タグの for で input を指定する
  • バリデーションエラーを WAI-ARIA で指定する
場合下記のようになります。
import { useId } from "react";

const PasswordInput = () => {
  const id = useId();
  const inputId = id + "_input";
  const errorMessageId = id + "_errorMessage";

  return (
    <>
      <label htmlFor={inputId}>パスワード</label>
      <input
        id={inputId}
        type="password"
        autoComplete="current-password"
        value={props.value}
        onChange={props.onChange}
        aria-invalid={props.hasError}
        aria-errormessage={props.errorMessageId}
      />
      {props.errorMessage !== undefined && (
        <span id={errorMessageId}>{errorMessage}</span>
      )}
    </>
  );
};

単体テストの書き方

恥ずかしながら今まで 1 つしかレンダリングされない前提で、ID を定数で実装していました。テストもその形で作っていたので動的な ID の場合に取得する方法がわからず、しばらく testing-library のドキュメントを探し回っていました。
結果として、 @testing-library/jest-dom の便利マッチャーを使うとシンプルで宣言的にテストを書くことが出来ました。
(jest 29, jest-dom 5 を使用しています)
describe("Password", () => {
  // ...
  describe("has error", () => {
    // ...
    test("has error message", () => {
      // ...
      expect(passwordInput).toBeInvalid();
      expect(passwordInput).toHaveErrorMessage(
        "ID またはパスワードが誤っています"
      );
    });
  });
});

まとめ

  • ID 系の HTML Attribute を追加したい場合は useId を使ってみよう
  • @testing-library/jest-dom やっぱり便利

ホームプロフィール外部リンクのため、別ウインドウで開きますプライバシーポリシー

© 2023 Oishi Takanori / Made with Gatsby.js