Learnitweb

React useId hook

1. Introduction

useId is a React Hook for generating unique IDs that can be passed to accessibility attributes.

const id = useId()

useId() does not take any parameter and returns a unique ID string associated with this particular useId call in this particular component.

Let us understand the usage of useId with the help of an example. HTML accessibility attributes, such as aria-describedby, allow you to define a relationship between two tags. For instance, you can indicate that an element, like an input, is explained by another element, such as a paragraph.

<label>
  Password:
  <input
    type="password"
    aria-describedby="password-hint"
  />
</label>
<p id="password-hint">
  The password should contain at least 8 characters and minimum 3 digits.
</p>

If you notice, ids are hardcoded here. It is possible that a component is rendered more than once on a page. In this case, Ids can not be unique if these are hard coded. The useId can help here.
You can generate Ids using useId. So even if the component is rendered multiples times on the page, the Ids won’t be duplicate.

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 8 characters and minimum 3 digits.
      </p>
    </>
  );
}

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

Note: Do not call useId to generate keys in a list. Keys should be generated from your data.

2. Generating IDs for several related elements

If you need to give IDs to multiple related elements, you can call useId to generate a shared prefix for them.

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

3. Assigning a common prefix to all generated IDs

When rendering multiple independent React applications on a single page, you should pass identifierPrefix as an option to your createRoot or hydrateRoot calls. This approach prevents ID collisions by ensuring that every ID generated with useId starts with the unique prefix you’ve provided.

index.js

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

index.html

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

App.js

import { useId } from 'react';

function PasswordField() {
  const passwordHintId = useId();
  console.log('Generated identifier:', passwordHintId)
  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 />
    </>
  );
}

4. What makes useId superior to an incrementing counter?

You might be curious why useId is preferable to incrementing a global variable like nextId++.

The main advantage of useId is its compatibility with server rendering. During server rendering, your components produce HTML output. Later, on the client side, hydration links your event handlers to this generated HTML. For successful hydration, the client output must precisely match the server HTML.

Using an incrementing counter makes this difficult because the order in which Client Components are hydrated might not align with the order in which the server HTML was produced. By using useId, you ensure that hydration will work correctly and the outputs from the server and client will be consistent.

React generates useId based on the “parent path” of the calling component. Therefore, if the client and server trees are identical, the “parent path” will align regardless of the rendering order.

5. Conclusion

In conclusion, the useId hook in React is a powerful tool for generating unique IDs that are consistent between server and client rendering. It ensures that your components maintain their identity and functionality across different rendering environments, making it an essential feature for building robust and accessible React applications. By using useId, you can avoid common pitfalls associated with ID conflicts and mismatches, ensuring a smoother development experience and a more reliable user interface.