Menu

React useIdの使い方:ラベルとARIAのための一意のID

useIdはコンポーネントのインスタンスごとに一意で安定したIDを返すので、コンポーネントが何度も現れても、ラベル、入力欄、aria-describedbyのヒントがお互いを指せます。Math.randomやカウンターがサーバーレンダリングで壊れる理由と、useIdがリストのkey用ではない理由を学びます。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

useId は、コンポーネントのインスタンスごとに一意のIDを返し、すべてのレンダリングで同じIDを返します。コンポーネントがページに複数回現れる可能性があるときに、<label> をその <input> に、あるいは入力欄をそのヒントに aria-describedby でつなぐために使います。

同じコンポーネントが2回描画され、2つの違うIDを受け取り、それぞれの項目の下に表示しています。「Confirm password」というテキストをクリックすると、フォーカスは2つ目の入力欄に移ります。その htmlFor が、その入力欄の id とだけ一致するからです。useId() の代わりに id="password" を直書きすると、両方のラベルが1つ目の入力欄を指してしまいます。

構文

const id = useId();

useId は引数を取らず、文字列を返します。ほかのすべてのフックと同じく、コンポーネントのトップレベルで呼びます。その正確な形式は内部のもので、バージョン間で変わってきました。React 18は :r1: を生成し、React 19.2は、ブラウザで初めて描画されたコンポーネントには _r_1_ を、サーバーで描画されたコンポーネントにはツリーの位置から組み立てた _R_ で始まるIDを生成します。決してパースしたり、その形に依存したりしないでください。

Math.randomやカウンターを使わない理由

アクセシビリティのためのIDは、サーバーが送るHTMLと、Reactがブラウザで組み立てるツリーとで一致しなければなりません。IDを作るわかりやすい2つの方法は、どちらもこの条件を満たせません。

// Changes on every render, and differs between server and browser
const id = 'field-' + Math.random().toString(36).slice(2);

// The server's counter keeps growing across requests, the browser starts at 0
let nextId = 0;
const id = 'field-' + nextId++;

サーバーレンダリング(Next.js、React Routerのフレームワークモード、あらゆる hydrateRoot の構成)では、サーバーがHTMLに id="field-4817" を出力し、ブラウザの最初のレンダリングは field-0 を計算して、Reactはハイドレーションの不一致を報告します。useId はツリー内でのコンポーネントの位置からIDを組み立てるので、両側でまったく同じになります。

サーバーがなくても、レンダリング中に作ったIDはレンダリングのたびに変わることがあります。次の例は、サーバーなしでその違いを示します。

ボタンを何回かクリックしてください。useId の値は変わりませんが、カウンターのIDはレンダリングのたびに増えるので、古いIDを指していたもの(aria-describedby やラベル)は、もう何も指さなくなります。カウンターを useState(() => nextId++) で包めば再レンダリングの問題は直りますが、サーバーとの不一致は直りません。

1回の呼び出しから複数のID

複数の項目を持つコンポーネントに、複数の useId 呼び出しは必要ありません。元のIDを1つ生成し、要素ごとに接尾辞を付けます。

メール欄に @ のない単語を入力してください。エラーが表示され、入力欄の aria-describedby がそれを指すので、入力欄にフォーカスしたときにスクリーンリーダーがエラーを読み上げます。App で <SignupForm /> を2回描画すると、それぞれのコピーが自分の元のIDを受け取ります。

リストのkey用ではない

keyとIDは別の問題を解決します。keyはレンダリング間でどの項目がどれかをReactに伝えるので、データから取る必要があります。useId はコンポーネントのインスタンスごとに1つのIDを与えるもので、map の中では呼べません。

// Wrong: breaks the rules of hooks, and the key is unrelated to the item
{todos.map((todo) => <Todo key={useId()} todo={todo} />)}

// Right: the key comes from the data
{todos.map((todo) => <Todo key={todo.id} todo={todo} />)}

データにIDがないときは、レンダリング中ではなく、項目を作るときに作ってください(項目を追加するイベントハンドラで crypto.randomUUID())。keyが項目と一緒にあり続けなければならない理由は、リストとkeyのページで説明しています。

1つのページに複数のReactルート

2つの別々のReactアプリが同じページに描画されると、IDが衝突する可能性があります。それぞれのルートに接頭辞を与えてください。

createRoot(document.getElementById('cart'), { identifierPrefix: 'cart-' });
createRoot(document.getElementById('chat'), { identifierPrefix: 'chat-' });

サーバーレンダリングでは、サーバーのレンダラーと hydrateRoot に同じ identifierPrefix を渡し、両側で同じIDが生成されるようにします。

よくある間違い

サーバーとブラウザで違うツリーを描画する。useId はコンポーネントの位置に依存するので、項目の上に typeof window === 'undefined' ? <A /> : <B /> のような分岐があると、2つのレンダリングの間でIDがずれることがあります。ハイドレーション中はツリーを同じに保ち、切り替えはその後、エフェクトで行ってください。

IDで要素を探す。document.getElementById(id) でも動きますが、DOMノードに触れるReactの方法はrefで、IDはまったく必要ありません。

ランダムな値として使う。このIDはアプリ内で一意なだけでランダムではなく、ツリーから予測できます。セキュリティトークン、キャッシュのキー、セッションをまたいで保存するものには使わないでください。

使うべきとき

再利用するコンポーネントが id 属性を必要とするときは、いつでも useId を使ってください。何度も使われるコンポーネントで作るフォームの項目、aria-describedby でつなぐツールチップ、aria-labelledby を持つダイアログ、aria-controls を持つタブなどです。IDがラベルと入力欄をつなぐだけなら、IDを使わずに入力欄をラベルの中に入れる方法もあります(<label>Name <input /></label>)。要素をネストできないときに useId を使ってください。

よくある質問

ReactのuseIdは何をするものですか?

そのコンポーネントのインスタンスに固有で、すべてのレンダリングで同じままの文字列を返します。要素をIDでつなぐために使います。ラベルの htmlFor、入力欄の aria-describedby、ダイアログの aria-labelledby などです。

IDにMath.random()やカウンターを使わないのはなぜですか?

サーバーとブラウザで違う値になるので、サーバーで描画したページとハイドレーションされた版が食い違い、Reactがハイドレーションの不一致を報告するからです。Math.random() はレンダリングのたびにも変わります。useId はツリー内でのコンポーネントの位置からIDを導くので、どちらでも同じになります。

useIdをリストのkeyに使えますか?

いいえ。keyはデータから取る必要があり、そうすることでReactはレンダリング間で同じ項目を対応させられます。useId はコンポーネントごとに1回呼ぶもので、map の中で呼ぶとそもそもフックのルールに反します。項目自身のIDを使ってください。

1回のuseId呼び出しから複数のIDを得るには?

useId を1回呼び、接尾辞を付けます:${id}-name、${id}-email。元のIDが一意なので、接尾辞を付けた文字列も一意です。

useIdのIDをCSSセレクタに使えますか?

避けてください。このIDはDOM内の要素をつなぐためのもので、その正確な形式は内部の詳細であり、Reactのバージョン間で変わってきました。スタイルはクラスで付け、要素は querySelector ではなくrefで参照してください。

Coddyのプログラミング言語のイラスト

Coddyでコードを学ぼう

始める