useId é um Hook do React para gerar IDs únicos que podem ser passados para atributos de acessibilidade.

const id = useId()

Referência

useId()

Chame useId no nível superior do seu componente para gerar um ID único:

import { useId } from 'react';

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

Veja mais exemplos abaixo.

Parâmetros

useId não aceita nenhum parâmetro.

Retorna

useId retorna uma sequência de ID único associada a esta chamada useId específica neste componente específico.

Ressalvas

  • useId é um Hook, então você só pode chamá-lo no nível superior do seu componente ou no seus próprios Hooks. Você não pode chamá-lo dentro de loops ou condições. Se precisar, extraia um novo componente e mova o estado para ele.

  • useId não deve ser usado para gerar chaves em uma lista. As chaves devem ser geradas a partir de seus dados.


Uso

Pitfall

Não chame useId para gerar chaves em uma lista. As chaves devem ser geradas a partir de seus dados.

Gerando IDs únicos para atributos de acessibilidade

Chame useId no nível superior do seu componente para gerar um ID único:

import { useId } from 'react';

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

Você pode então passar o ID gerado para diferentes atributos:

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

Vamos analisar um exemplo para ver quando isso é útil.

Atributos de acessibilidade do HTML como aria-describedby permite especificar que duas tags estão relacionadas entre si. Por exemplo, você pode especificar que um elemento (como um input) seja descrito por outro elemento (como um parágrafo).

No HTML normal, você escreveria assim:

<label>
Senha:
<input
type="password"
aria-describedby="password-hint"
/>
</label>
<p id="password-hint">
A senha deve conter pelo menos 18 caracteres
</p>

No entanto, codificar IDs como esse não é uma boa prática no React. Um componente pode ser renderizado mais de uma vez na página, mas os IDs devem ser únicos! Em vez de codificar um ID, gere um ID único com useId:

import { useId } from 'react';

function PasswordField() {
const passwordHintId = useId();
return (
<>
<label>
Senha:
<input
type="password"
aria-describedby={passwordHintId}
/>
</label>
<p id={passwordHintId}>
A senha deve conter pelo menos 18 caracteres
</p>
</>
);
}

Agora, mesmo que PasswordField apareça várias vezes na tela, os IDs gerados não entrarão em conflito.

import { useId } from 'react';

function PasswordField() {
  const passwordHintId = useId();
  return (
    <>
      <label>
        Senha:
        <input
          type="password"
          aria-describedby={passwordHintId}
        />
      </label>
      <p id={passwordHintId}>
        A senha deve conter pelo menos 18 caracteres
      </p>
    </>
  );
}

export default function App() {
  return (
    <>
      <h2>Escolha uma senha</h2>
      <PasswordField />
      <h2>Confirme a senha</h2>
      <PasswordField />
    </>
  );
}

Assista à esse vídeo para ver a diferença na experiência do usuário com tecnologias assistivas.

Pitfall

Com a renderização do servidor, useId requer uma árvore de componentes idêntica no servidor e no cliente. Se as árvores que você renderizar no servidor e no cliente não corresponderem exatamente, os IDs gerados não corresponderão.

Deep Dive

Por que useId é melhor que um contador de incremento?

Você pode estar se perguntando por que useId é melhor do que incrementar uma variável global como nextId++.

O principal benefício do useId é que o React garante que funcione com a renderização do servidor. Durante a renderização do servidor, seus componentes geram saídas HTML. Posteriormente, no cliente, a hidratação anexa seus manipuladores de eventos ao HTML gerado. Para que a hidratação funcione, a saída do cliente deve corresponder ao HTML do servidor.

Isto é muito difícil de garantir com um contador incremental porque a ordem em que os componentes do cliente são hidratados pode não corresponder à ordem em que o HTML do servidor foi emitido. Ao chamar useId, você garante que a hidratação funcionará e que a saída corresponderá entre o servidor e o cliente.

Dentro do React, useId é gerado a partir do “caminho pai” do componente chamado. É por isso que, se o cliente e a árvore do servidor forem iguais, o “caminho pai” corresponderá, independentemente da ordem de renderização.


Se você precisar fornecer IDs para vários elementos relacionados, você pode chamar useId para gerar um prefixo compartilhado para eles:

import { useId } from 'react';

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

Isso permite que você evite chamar useId para cada elemento que precisa de um ID único.


Especificando um prefixo compartilhado para todos os IDs gerados

Se você renderizar várias aplicações React independentes em uma única página, passe identifierPrefix como uma opção para suas chamadas createRoot ou hydrateRoot. Isso garante que os IDs gerados pelos dois aplicativos diferentes nunca entrem em conflito porque cada identificador gerado com useId começará com o prefixo distinto que você especificou.

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


Using the same ID prefix on the client and the server

If you render multiple independent React apps on the same page, and some of these apps are server-rendered, make sure that the identifierPrefix you pass to the hydrateRoot call on the client side is the same as the identifierPrefix you pass to the server APIs such as 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' }
);

You do not need to pass identifierPrefix if you only have one React app on the page.