1. ホーム
  2. React

【React】useId の使い方|フォームのラベルとアクセシビリティで一意なIDを生成する

Share

フォームを作るとき、labelhtmlForinputid を同じ値で結びつけると、ラベルをクリックしただけで入力欄にフォーカスが移り、スクリーンリーダーもラベルと入力欄の対応を正しく読み上げます。ところが、この id"email" のような固定値で書いてしまうと、同じコンポーネントを1つの画面に2つ置いたときに id が重複してしまいます。これを防ぐために React 18 で追加されたのが useId です。この記事では、useId とは何か、なぜ必要なのか、基本の使い方から1つのIDを複数箇所で使い分ける方法、SSRとの関係、そして避けるべき使い方までを初心者向けに解説します。

useId とは何か

useId は、コンポーネントの中で一意なID文字列を生成するためのReactフックです。React 18 で追加されました。引数は取らず、呼び出すとそのコンポーネントインスタンスに紐づいた固有の文字列を返します。同じコンポーネントを画面に複数配置しても、それぞれのインスタンスで異なるIDが生成されるため、ID同士がぶつかりません。

basic.tsx
import { useId } from "react";

function Example() {
  // コンポーネントのトップレベルで呼び出す
  const id = useId();
  // id には ":r0:" のような一意な文字列が入る
  return <p>{id}</p>;
}

useId は他のフックと同じく、コンポーネント関数のトップレベルで呼び出す必要があります。ループや条件分岐の中では呼び出せません。返ってくる文字列は、開発者が中身を組み立てるための値ではなく、「他とかぶらないことだけが保証された不透明な識別子」だと考えてください。実際に生成される文字列にはコロン(:)などReact内部の文字が含まれ、その形式に依存したコードを書くべきではありません。

固定IDだと何が困るのか

labelinput を結びつけるには、labelhtmlForinputid に同じ値を指定します。これはアクセシビリティの基本で、ラベルクリックでの入力欄フォーカスや、支援技術による正しい読み上げに欠かせません。しかし、その値を固定文字列にすると問題が起こります。

EmailField-bad.tsx
// 固定IDの例:このコンポーネントを2つ並べると id が重複する
function EmailField() {
  return (
    <div>
      <label htmlFor="email">メールアドレス</label>
      <input id="email" type="email" />
    </div>
  );
}

id はHTMLのドキュメント内で一意でなければならない属性です。上の EmailField を1つの画面に2回描画すると、id="email" が2つ存在することになり、仕様違反になります。この状態では、たとえば2つ目のラベルをクリックしても、ブラウザは同じ id を持つ最初の要素にフォーカスしてしまい、意図した入力欄が反応しません。aria-describedby で入力欄に補足説明を結びつけている場合も、参照先が入り乱れて正しく機能しなくなります。フォームコンポーネントを再利用できる部品として作るほど、この重複は起こりやすくなります。

useId でラベルと入力欄を結びつける

useId で生成したIDを、labelhtmlForinputid両方に同じ変数として渡すのが基本の使い方です。こうすれば、コンポーネントが何個あってもインスタンスごとに違うIDになり、重複しません。

EmailField.tsx
import { useId } from "react";

function EmailField() {
  // このインスタンス固有のIDを生成する
  const id = useId();

  return (
    <div>
      <label htmlFor={id}>メールアドレス</label>
      <input id={id} type="email" />
    </div>
  );
}

htmlFor={id}id={id} が同じ値を指すので、ラベルと入力欄がきちんと結びつきます。そして EmailField を画面に2つ置いても、それぞれの useId() が別々の文字列を返すため、IDがかぶることはありません。開発者はIDの中身を意識する必要がなく、「ラベルと入力欄を確実に対応させる」という目的だけに集中できます。

1つのIDから複数のIDを派生させる

1つのフォームに複数の入力欄があるとき、入力欄ごとに useId を何度も呼ぶこともできますが、React公式は別のやり方を推奨しています。useId1回だけ呼び、その値を接頭辞(プレフィックス)として使い回す方法です。${id}-firstname のようにテンプレートリテラルで接尾辞を足せば、関連するIDをまとめて作れます。

NameForm.tsx
import { useId } from "react";

function NameForm() {
  // 1回だけ呼び出し、接頭辞として使う
  const id = useId();

  return (
    <form>
      <label htmlFor={`${id}-firstname`}>名</label>
      <input id={`${id}-firstname`} type="text" />

      <label htmlFor={`${id}-lastname`}>姓</label>
      <input id={`${id}-lastname`} type="text" />
    </form>
  );
}

useId() が返す値がインスタンスごとに一意なので、それを接頭辞にした "○○-firstname""○○-lastname" も自動的に一意になります。入力欄が増えるたびに useId を呼び足す必要がなく、1つのIDから関連するIDをまとめて管理できるのが利点です。aria-describedby でエラーメッセージや補足文を結びつけるときにも、${id}-error のように同じ接頭辞を使えば、対応関係が崩れません。

サーバーサイドレンダリングとの関係

useId がわざわざ用意された背景には、サーバーサイドレンダリング(SSR)でのハイドレーション不一致という問題があります。SSRでは、まずサーバー側でHTMLを生成し、ブラウザ側でReactがそのHTMLに機能を結びつけます(ハイドレーション)。このとき、サーバーで生成したIDとクライアントで生成したIDが食い違うと、Reactは不一致を検出して警告を出し、画面が正しく動かないことがあります。

useId は、このサーバーとクライアントで同じIDが生成されるように設計されているため、ハイドレーションの不一致を避けられます。自前で乱数やカウンターを使ってIDを作ると、サーバーとクライアントで値がずれてこの問題が起きますが、useId を使えばReactがツリー内での位置に基づいて安定したIDを割り当てるため、両者が一致します。SSRを使うNext.jsのようなフレームワークでフォームのIDを生成するなら、useId が適した選択肢になります。

useId を使ってはいけない場面

useId は便利ですが、用途は「HTML属性用の一意なID生成」に限られます。次の2つは公式にも明記されている、避けるべき使い方です。

リストの key には使わない

リストを描画するときの keyuseId の値を使ってはいけません。key は、配列の各項目を識別してReactが差分を管理するためのもので、データそのものから導くべき値です。useId はコンポーネントインスタンスに紐づくIDであり、リストの個々のデータに対応するものではありません。key には、データが持つ一意な値(データベースのIDなど)を使ってください。

CSSセレクタや querySelector には使わない

useId が生成する文字列にはコロン(:)が含まれます。コロンはCSSセレクタでは擬似クラスなどの特別な意味を持つため、生成されたIDをそのままCSSのセレクタや document.querySelector に渡すと、正しく解釈されずに動作しません。IDでスタイルを当てたり要素を取得したりしたい場合は、useId ではなく自分で決めた固定のIDやクラス名を使ってください。useId の値は、あくまで htmlForidaria-* 属性に文字列として渡す用途に留めるのが安全です。

まとめ

useId は、React 18 で追加された、一意なID文字列を生成するためのフックです。labelhtmlForinputid を結びつけたり、aria-describedby で補足説明を関連づけたりするときに、固定IDによる重複を防げます。基本は const id = useId(); で作った値を htmlForid の両方に渡すだけ。複数の入力欄があるときは、1回だけ呼んだ値を ${id}-firstname のように接頭辞として使い回すのが公式推奨のやり方です。サーバーとクライアントで同じIDが生成される設計なのでSSRのハイドレーション不一致も避けられます。一方で、リストの key やCSSセレクタには使わない、という点を守れば、アクセシブルなフォームを安全に組み立てられます。

参考ページ