HOW TO React

useId

useId — это React Hook для генерации уникальных ID, которые можно передавать в accessibility-атрибуты.

const id = useId()

Справочник

useId()

Вызывайте useId на верхнем уровне вашего компонента, чтобы сгенерировать уникальный ID:

import { useId } from 'react';

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

Параметры

useId не принимает никаких параметров.

Возвращаемое значение

useId возвращает уникальную строку ID, связанную с конкретным вызовом useId в этом конкретном компоненте.

Предостережения

  • useId — это Hook, поэтому вы можете вызывать его только на верхнем уровне вашего компонента или в собственных Hooks. Вы не можете вызывать его внутри циклов или условий. Если вам это нужно, вынесите новый компонент и перенесите state в него.

  • useId не следует использовать для генерации cache keys для use(). ID стабилен, пока компонент смонтирован, но может изменяться во время rendering. Cache keys следует генерировать из ваших данных.

  • useId не следует использовать для генерации keys в списке. Keys должны генерироваться из ваших данных.

  • useId в настоящее время нельзя использовать в async Server Components.


Использование

Не вызывайте useId для генерации keys в списке. Keys должны генерироваться из ваших данных.

Генерация уникальных ID для accessibility-атрибутов

Вызывайте useId на верхнем уровне вашего компонента, чтобы сгенерировать уникальный ID:

import { useId } from 'react';

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

Затем вы можете передать сгенерированный ID в разные атрибуты:

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

Давайте разберём пример, чтобы увидеть, когда это полезно.

HTML accessibility-атрибуты вроде aria-describedby позволяют указать, что два тега связаны друг с другом. Например, можно указать, что один элемент (например, input) описывается другим элементом (например, абзацем).

В обычном 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 вручную, сгенерируйте уникальный ID с помощью useId:

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 />
    </>
  );
}

При server rendering useId требует идентичного дерева компонентов на сервере и на клиенте. Если деревья, которые вы рендерите на сервере и на клиенте, не совпадают в точности, сгенерированные ID тоже не совпадут.

Почему useId лучше, чем увеличивающийся счётчик?

Вы можете задаться вопросом, почему useId лучше, чем увеличение глобальной переменной вроде nextId++.

Главное преимущество useId в том, что React гарантирует его совместимость с server rendering. Во время server rendering ваши компоненты генерируют HTML-вывод. Позже, на клиенте, hydration привязывает обработчики событий к сгенерированному HTML. Чтобы hydration работал, клиентский вывод должен совпадать с HTML, полученным с сервера.

Это очень трудно гарантировать с увеличивающимся счётчиком, потому что порядок, в котором Client Components гидратируются, может не совпадать с порядком, в котором серверный HTML был выведен. Используя useId, вы гарантируете, что hydration сработает, а вывод на сервере и клиенте будет совпадать.

Внутри React useId генерируется из "parent path" вызывающего компонента. Поэтому, если дерево на клиенте и на сервере одинаковое, "parent path" совпадёт независимо от порядка rendering.


Если вам нужно назначить 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>
  );
}

Так вы избегаете вызова useId для каждого отдельного элемента, которому нужен уникальный ID.


Указание общего префикса для всех сгенерированных ID

Если вы рендерите несколько независимых React-приложений на одной странице, передайте identifierPrefix как опцию в вызовы createRoot или hydrateRoot. Это гарантирует, что ID, сгенерированные двумя разными приложениями, никогда не конфликтуют, потому что каждый идентификатор, созданный с помощью useId, будет начинаться с указанного вами уникального префикса.


//index.html
<!DOCTYPE html>
<html>
  <head><title>My app</title></head>
  <body>
    <div id="root1"></div>
    <div id="root2"></div>
  </body>
</html>

Использование одного и того же префикса ID на клиенте и сервере

Если вы рендерите несколько независимых React-приложений на одной странице, и некоторые из этих приложений серверно-рендерятся, убедитесь, что identifierPrefix, который вы передаёте в hydrateRoot на стороне клиента, совпадает с identifierPrefix, который вы передаёте в server APIs вроде renderToPipeableStream.

// 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-приложение, передавать identifierPrefix не нужно.

On this page