useId — це хук для генерації унікальних ідентифікаторів (далі — ID), які можуть передаватися як атрибути доступності.

const id = useId()

Опис

useId()

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

import { useId } from 'react';

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

Перегляньте більше прикладів нижче.

Параметри

useId не приймає жодних параметрів.

Результат

useId повертає унікальну ID-стрічку, пов’язану з конкретним запитом useId в цьому конкретному компоненті.

Застереження

  • useId — це хук, тож він може викликатися тільки на верхньому рівні вашого компонента або у ваших власних хуках. Ви не можете викликати його в циклах або умовно. Якщо ж є така потреба, то виокреміть новий компонент та перемістіть у нього стан.

  • 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, дають змогу зазначити, що два теги пов’язані один з одним. Наприклад, ви можете зазначити, що елемент (як-от input) описаний іншим компонентом (як-от параграф).

У звичайному HTML ви би написали наступне:

<label>
Password:
<input
type="password"
aria-describedby="password-hint"
/>
</label>
<p id="password-hint">
Довжина паролю має бути не менш ніж 18 символів
</p>

Однак, таке задання ID не найкраща практика в React. Компонент може бути відрендерений на сторінці більш ніж один раз — і 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}>
Довжина паролю має бути не менш ніж 18 символів
</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}>
          Довжина паролю має бути не менш ніж 18 символів
      </p>
    </>
  );
}

export default function App() {
  return (
    <>
      <h2>Введіть пароль</h2>
      <PasswordField />
      <h2>Підтвердіть пароль</h2>
      <PasswordField />
    </>
  );
}

Перегляньте відео, щоб побачити різницю користувацького досвіду з допоміжними технологіями.

Будьте обачні

Для серверного рендерингу, useId потребує ідентичного дерева компонентів на сервері та на клієнті. Якщо дерева, які ви рендерите на сервері та на клієнті, не збігаються, то і згенеровані ID не будуть збігатись.

Занурення

Чому використання useId краще за інкрементний лічильник?

Можливо, вам кортить дізнатися, чому використання useId краще за інкрементування глобальної змінної, як наприклад, nextId++.

Початкова перевага 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 + '-name'}>Ім'я:</label>
      <input id={id + '-name'} type="text" />
      <hr />
      <label htmlFor={id + '-lastName'}>Прізвище:</label>
      <input id={id + '-lastName'} type="text" />
    </form>
  );
}

Це дасть змогу виклику useId для кожного окремого елементу, якому треба унікальне ID.


Визначення спільного префікса для всіх згенерованих ID

Якщо ви рендерите декілька незалежних React-застосунків на одній сторінці, передавайте identifierPrefix як опцію до вашого createRoot або hydrateRoot. Це забезпечить те, що 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-застосунків на одній сторінці, і деякі з цих застосунків відрендерені сервером, перевірте, що identifierPrefix, який ви передаєте до hydrateRoot на клієнті, є тим самим identifierPrefix, який ви передаєте до серверних 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' }
);

Немає потреби передавати identifierPrefix, якщо ви маєте тільки один React-застосунок на сторінці.