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

const id = useId()

リファレンス

useId()

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

import { useId } from 'react';

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

以下の例をご覧ください。

パラメータ

useIdはパラメータを取りません。

戻り値

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

注意点

  • useIdはフックであるため、コンポーネントのトップレベルまたは独自のフックでのみ呼び出すことができます。ループ内や条件付きで呼び出すことはできません。それが必要な場合は、新しいコンポーネントを抽出し、状態をそこに移動してください。

  • useIdは、リスト内のキーを生成するために使用すべきではありませんキーはデータから生成する必要があります。


使い方

落とし穴

リスト内でキーを生成するためにuseIdを呼び出さないでください。 キーはデータから生成されるべきです。

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

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

import { useId } from 'react';

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

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

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

これがいつ役立つかを確認するために、例を見ていきましょう。

HTMLアクセシビリティ属性、例えばaria-describedbyを使用すると、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内部では、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アプリケーションをレンダリングする場合は、createRootまたはhydrateRootの呼び出しにオプションとしてidentifierPrefixを渡します。これにより、2つの異なるアプリで生成されたIDが衝突しないようになります。これは、useIdで生成されたすべての識別子が、指定した異なるプレフィックスで始まるためです。

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が、サーバーAPI(例えばrenderToPipeableStreamなど)に渡す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を渡す必要はありません。