React のアクセシビリティ改善に役立つユニークIDの生成
はじめに
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 やっぱり便利