useId 完全入門【React】アクセシビリティのためのユニークID生成

ReactのuseIdとは何か、フォームのラベルとinputを正しく紐付けるためのユニークID生成の仕組みを解説。サーバーサイドレンダリングでのハイドレーション問題の解決方法も紹介します。

#react#hooks#useid#javascript#frontend

「フォームの labelinput を紐付けるとき、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>
  );
}

labelhtmlForinputid を同じ値にすることで、クリックするとフォームにフォーカスが当たるアクセシブルなフォームが作れます。

複数の要素に使う場合

一つのコンポーネントに複数の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回の戻り値にサフィックスを付ける方がシンプルです。

アクセシビリティへの応用

useIdaria-* 属性との組み合わせにも便利です。

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 環境でも安全
  • htmlForidaria-describedby など、要素同士を紐付けるのに使う
  • 複数のIDが必要な場合は1つの useId にサフィックスを付けて使う
  • リストのキーには使ってはいけない(データ由来のIDを使う)

PR

useId 公式ドキュメントで詳細を確認しよう

useIdのインタラクティブなサンプルや、複数のReactアプリが同じページに存在する場合の設定は公式ドキュメントで確認できます。

useId 公式ドキュメントを見る

関連記事