4 Reasons You Should Be Using useId in React

in #javascriptlast month


image.png

1. Assigning distinct identifiers for elements used with assistive technologies.

Before

In web development, accessibility features like aria-describedby allow you to connect different parts of your webpage. This is helpful for users with screen readers or other assistive technologies.

Imagine you have a form with an input field, but without any explanation. With aria-describedby, you can link that input to a paragraph that explains what information to enter. This way, everyone can understand your webpages clearly.

Here’s an example of how it works in regular HTML code, but with a simpler explanation.

<label>
  Password:
  <input
    type="password"
    aria-describedby="password-hint"
  />
</label>
<p id="password-hint">
  The password should contain at least 18 characters
</p>

After

While HTML offers features like aria-describedby to link related elements (think an input field and its descriptive paragraph), in React, directly assigning IDs (like in <input id="description-input" ...>) isn't ideal. This is because React components can render multiple times on a page, and IDs need to be unique. To overcome this, React provides the useId hook to generate unique IDs dynamically:

import { useId } from 'react';

interface PasswordFieldProps {}

const SomeField: React.FC<PasswordFieldProps> = () => {
  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 SomeField;

2. Generating multiple unique IDs for a set of related elements.

To ensure unique IDs for connected elements in React, use the useId hook to create a common prefix.

import { useId } from 'react';

interface FormProps {}

const Form: React.FC<FormProps> = () => {
  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>
  );
};

export default Form;

3. Defining a common identifier prefix for all generated IDs.

Imagine having two separate React applications running on the same webpage. To prevent conflicts between their IDs (generated using useId), you can use the identifierPrefix option during createRoot or hydrateRoot calls. This lets you specify a unique prefix for each app, ensuring their generated IDs never clash, even when they share the same page.

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) // Here we obtain the gen id
  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 />
    </>
  );
}

console.log Output

As you can see, the ids are unique per app.

Generated identifier: :my-first-app-r0:
Generated identifier: :my-second-app-r1:

4. Ensuring identical ID prefixes are used on both the client and server for seamless communication.

Avoid ID conflicts in mixed server-rendered and client-side React apps by using the same identifierPrefix for hydrateRoot and server APIs.

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

The identifierPrefix is only needed when using multiple React apps together.

Warning

Do not use to generate keys in a list. Keys should be generated from your data.


If you liked this content I’d appreciate an upvote or a comment. That helps me improve the quality of my posts as well as getting to know more about you, my dear reader.

Muchas gracias!

Follow me for more content like this.

X | PeakD | Rumble | YouTube | Linked In | GitHub | PayPal.me | Medium

Down below you can find other ways to tip my work.

BankTransfer: "710969000019398639", // CLABE
BAT: "0x33CD7770d3235F97e5A8a96D5F21766DbB08c875",
ETH: "0x33CD7770d3235F97e5A8a96D5F21766DbB08c875",
BTC: "33xxUWU5kjcPk1Kr9ucn9tQXd2DbQ1b9tE",
ADA: "addr1q9l3y73e82hhwfr49eu0fkjw34w9s406wnln7rk9m4ky5fag8akgnwf3y4r2uzqf00rw0pvsucql0pqkzag5n450facq8vwr5e",
DOT: "1rRDzfMLPi88RixTeVc2beA5h2Q3z1K1Uk3kqqyej7nWPNf",
DOGE: "DRph8GEwGccvBWCe4wEQsWsTvQvsEH4QKH",
DAI: "0x33CD7770d3235F97e5A8a96D5F21766DbB08c875"