useId 完全入門【React】アクセシビリティのためのユニークID生成
ReactのuseIdとは何か、フォームのラベルとinputを正しく紐付けるためのユニークID生成の仕組みを解説。サーバーサイドレンダリングでのハイドレーション問題の解決方法も紹介します。
「フォームの label と input を紐付けるとき、IDをどう決めればいい?」——Reactでコンポーネントを作っていると、ユニークなIDが必要な場面によく遭遇します。
useId はこの問題を解決するためのフックです。サーバーとクライアントで一致するユニークなIDを自動生成してくれます。
この記事でわかること:
useIdが必要な理由(なぜMath.random()や連番はダメなのか)- 基本的な使い方
- 複数のID要素への応用
- リストのキーには使ってはいけない理由
useId とは?
useId は、アクセシビリティ属性などに使えるユニークなIDを生成するフックです。
const id = useId();パラメータはありません。戻り値は :r0:, :r1: のような形式のユニークな文字列です。
なぜ useId が必要なのか?
問題1:Math.random() を使うとSSRで壊れる
// ❌ NG:サーバーとクライアントで異なるIDが生成される
function EmailField() {
const id = Math.random(); // サーバー: 0.123... クライアント: 0.456...
return (
<>
<label htmlFor={id}>メールアドレス</label>
<input id={id} type="email" />
</>
);
}Next.js などの SSR 環境では、サーバーで生成したIDとクライアントで生成したIDが異なるとハイドレーションエラーが発生します。
問題2:グローバルカウンタはコンポーネントの再利用に弱い
// ❌ NG:同じコンポーネントを複数使うと ID が重複することがある
let counter = 0;
function EmailField() {
const id = `email-field-${counter++}`; // コンテキストによって不安定
}useId を使うと、コンポーネントごと・フック呼び出しごとに安定したユニークIDが保証されます。
基本的な使い方
import { useId } from "react";
function EmailField() {
const id = useId();
return (
<div>
<label htmlFor={id}>メールアドレス</label>
<input
id={id}
type="email"
placeholder="example@mail.com"
/>
</div>
);
}label の htmlFor と input の id を同じ値にすることで、クリックするとフォームにフォーカスが当たるアクセシブルなフォームが作れます。
複数の要素に使う場合
一つのコンポーネントに複数のIDが必要なとき、同じ useId の戻り値をプレフィックスとして使うのが効率的です。
import { useId } from "react";
function PasswordField() {
const id = useId();
return (
<div>
<label htmlFor={`${id}-password`}>パスワード</label>
<input
id={`${id}-password`}
type="password"
aria-describedby={`${id}-hint`}
/>
<p id={`${id}-hint`}>
8文字以上の英数字を入力してください。
</p>
</div>
);
}useId を複数回呼び出すより、1回の戻り値にサフィックスを付ける方がシンプルです。
アクセシビリティへの応用
useId は aria-* 属性との組み合わせにも便利です。
import { useId } from "react";
function FormField({ label, type = "text", hint }) {
const id = useId();
return (
<div>
<label htmlFor={id}>{label}</label>
<input
id={id}
type={type}
aria-describedby={hint ? `${id}-hint` : undefined}
/>
{hint && (
<p id={`${id}-hint`} style={{ fontSize: "0.8em", color: "gray" }}>
{hint}
</p>
)}
</div>
);
}
// 使用例
function ContactForm() {
return (
<form>
<FormField label="氏名" hint="フルネームで入力してください" />
<FormField label="電話番号" type="tel" hint="ハイフンなしで入力" />
<FormField label="メッセージ" />
</form>
);
}リストのキーには使ってはいけない
// ❌ NG:useId はリストのキーには使えない
function List({ items }) {
return (
<ul>
{items.map((item) => {
const id = useId(); // ← Hooksのルール違反(ループ内で使っている)
return <li key={id}>{item.name}</li>;
})}
</ul>
);
}
// ✅ OK:リストのキーはデータ由来のIDを使う
function List({ items }) {
return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}フックはループや条件分岐の中では使えません。また、リストのキーはデータに含まれる実際のIDを使うべきです。
まとめ
useIdはアクセシビリティ属性に使えるユニークなIDを生成するフック- サーバーとクライアントで一致するIDを生成するため、SSR 環境でも安全
htmlFor・id・aria-describedbyなど、要素同士を紐付けるのに使う- 複数のIDが必要な場合は1つの
useIdにサフィックスを付けて使う - リストのキーには使ってはいけない(データ由来のIDを使う)