useId は、アクセシビリティ属性に渡すことができる一意の ID を生成するための React フックです。

const id = useId()

リファレンス

useId()

コンポーネントのトップレベルで useId を呼び出して、一意の ID を生成します。

import { useId } from 'react';

function PasswordField() {
const passwordHintId = useId();
// ...

さらに例を見る

引数

useId は引数を受け取りません。

返り値

useId は、特定のコンポーネント内でこの特定の useId の呼び出しに関連付けられた、一意の ID 文字列を返します。

注意点

  • useId はフックであるため、コンポーネントのトップレベルまたは独自のフック内でのみ呼び出すことができます。ループまたは条件分岐内で呼び出すことはできません。もし必要な場合は、新しいコンポーネントを作成し、状態を移動させる必要があります。

  • useId を、リスト内の key の生成には使用しないでくださいkey はデータから生成される必要があります。


使用法

落とし穴

リスト内の key を生成するために useId を呼び出さないでください。key はデータから生成される必要があります。

アクセシビリティ属性のための一意の ID の生成

コンポーネントのトップレベルで useId を呼び出して、一意の ID を生成します。

import { useId } from 'react';

function PasswordField() {
const passwordHintId = useId();
// ...

その後、生成された ID をさまざまな属性に渡すことができます。

<>
<input type="password" aria-describedby={passwordHintId} />
<p id={passwordHintId}>
</>

これがどのような場合に役立つかを、例を通してみてみましょう。

aria-describedby のような HTML アクセシビリティ属性を使用すると、2 つのタグが相互に関連していることを指定することができます。例えば、入力フィールドのような要素が、段落などの別の要素で説明されていることを指定することができます。

通常の HTML では、次のように記述します。

<label>
Password:
<input
type="password"
aria-describedby="password-hint"
/>
</label>
<p id="password-hint">
The password should contain at least 18 characters
</p>

ただし、このように ID をハードコードすることは、React ではおすすめできません。コンポーネントはページ上で複数回レンダー可能ですが、ID は一意である必要があります! ID をハードコードするのではなく、useId を使用して一意の ID を生成します。

import { useId } from 'react';

function PasswordField() {
const passwordHintId = useId();
return (
<>
<label>
Password:
<input
type="password"
aria-describedby={passwordHintId}
/>
</label>
<p id={passwordHintId}>
The password should contain at least 18 characters
</p>
</>
);
}

これで、画面上に PasswordField が複数回表示される場合でも、生成される ID が同じになることはありません。

import { useId } from 'react';

function PasswordField() {
  const passwordHintId = useId();
  return (
    <>
      <label>
        Password:
        <input
          type="password"
          aria-describedby={passwordHintId}
        />
      </label>
      <p id={passwordHintId}>
        The password should contain at least 18 characters
      </p>
    </>
  );
}

export default function App() {
  return (
    <>
      <h2>Choose password</h2>
      <PasswordField />
      <h2>Confirm password</h2>
      <PasswordField />
    </>
  );
}

このビデオを見て支援技術による、ユーザ体験の違いを確認してみてください。

落とし穴

サーバレンダリングでは、useId にはサーバとクライアント上で同一のコンポーネントツリーが必要です。サーバ上とクライアント上でレンダーされるツリーが正確に一致しない場合、生成される ID は一致しません。

さらに深く知る

useId が増分カウンタよりも良い理由

useIdnextId++ のようなグローバル変数を増分するよりもなぜ良いのか、疑問に思われるかもしれません。

useId の主な利点は、React が、サーバレンダリングで確実に機能することを保証していることです。サーバでのレンダー中に、コンポーネントは HTML 形式の出力を生成します。その後クライアント上で、生成された HTML に、ハイドレーションによって、イベントハンドラがアタッチされます。ハイドレーションが機能するには、クライアントの出力がサーバの HTML と一致する必要があります。

クライアントコンポーネントがハイドレートされる順序は、サーバ HTML が出力された順序と一致しない可能性があるため、これをインクリメントカウンタで保証することは非常に困難です。useId を呼び出すことで、ハイドレーションが機能し、サーバとクライアントの間で出力が一致することが保証されます。

React 内部では、呼び出し元コンポーネントの “親のパス (parent path)” から useId が生成されます。そのため、クライアントとサーバのツリーが同じであれば、レンダー順序に関係なく “親のパス” が一致することになります。


複数の関連要素に ID を与える必要がある場合は、useId で生成した値を共有のプレフィックスとして使用できます。

import { useId } from 'react';

export default function Form() {
  const id = useId();
  return (
    <form>
      <label htmlFor={id + '-firstName'}>First Name:</label>
      <input id={id + '-firstName'} type="text" />
      <hr />
      <label htmlFor={id + '-lastName'}>Last Name:</label>
      <input id={id + '-lastName'} type="text" />
    </form>
  );
}

これにより、一意の ID を必要とするすべての要素に対して useId を呼び出す必要がなくなります。


生成されるすべての ID に共有プレフィックスを指定する

複数の独立した React アプリケーションを 1 つのページにレンダーする場合は、オプションとして identifierPrefixcreateRoot または hydrateRoot に渡します。これにより、useId で生成されたすべての識別子が指定した個別のプレフィックスで始まるため、2 つの異なるアプリによって生成された ID が衝突することがなくなります。

import { createRoot } from 'react-dom/client';
import App from './App.js';
import './styles.css';

const root1 = createRoot(document.getElementById('root1'), {
  identifierPrefix: 'my-first-app-'
});
root1.render(<App />);

const root2 = createRoot(document.getElementById('root2'), {
  identifierPrefix: 'my-second-app-'
});
root2.render(<App />);


クライアントとサーバで同じ ID プレフィックスを使う

もし同じページ上で複数の独立した React アプリをレンダーしており、そのうちいくつかがサーバでレンダーされる場合は、クライアント側の hydrateRoot 呼び出しに渡す identifierPrefix が、renderToPipeableStream などのサーバ API に渡す identifierPrefix と同じになるようにしてください。

// Server
import { renderToPipeableStream } from 'react-dom/server';

const { pipe } = renderToPipeableStream(
<App />,
{ identifierPrefix: 'react-app1' }
);
// Client
import { hydrateRoot } from 'react-dom/client';

const domNode = document.getElementById('root');
const root = hydrateRoot(
domNode,
reactNode,
{ identifierPrefix: 'react-app1' }
);

ページ内に React アプリが 1 つしかない場合は identifierPrefix を渡す必要はありません。