#react#accessibility#ssr#hooks#webperf

useId: Why You Should Stop Using Math.random() for IDs in React

Learn how React 18's useId hook solves critical accessibility and SSR consistency issues. Find out why Math.random() is a trap and how to use useId the right way.

useId: Why You Should Stop Using Math.random() for IDs in React

TL;DR: The useId hook generates unique, stable IDs that remain consistent between renders. It is essential for accessibility (linking labels to inputs) and prevents hydration errors in SSR (Server-Side Rendering), which Math.random() cannot guarantee.


If you've ever needed to link a <label> to an <input> in a reusable React component, you've likely faced the dilemma: "How do I generate a unique ID for this field?".

For a long time, the quick fix was using something like Math.random() or a global counter. But if you care about accessibility (a11y) and Server-Side Rendering (SSR), these approaches are actually traps.

With React 18, we got the useId hook, which is the official and definitive solution to this problem.


The Problem with "Magic" IDs

In plain HTML, linking a label to an input is straightforward: the label's for attribute must match the input's id.

<label for="email">Email:</label>
<input id="email" type="email" />

But in React, components are built to be reutilizáveis. If you render this component twice on the same page, you'll end up with two elements having id="email". This is invalid HTML and breaks assistive technologies like screen readers.

The Sin of Math.random()

Many try to "solve" this by generating a random ID during rendering:

// ❌ DON'T DO THIS
const id = Math.random().toString(36).substr(2, 9);

This causes two major issues:

  1. Instability: The ID changes on every render, which can confuse the browser and accessibility tools.
  2. Hydration Mismatch: In SSR, the Math.random() generated on the server will differ from the code on the client, leading to hydration errors.

Enter useId

useId generates a unique string that is stable across renders and, most importantly, consistent between the server and the client. It takes no arguments and returns the ID string.

import { useId } from 'react';

function LabeledInput({ label, type = "text" }) {
  const id = useId();
  
  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input id={id} type={type} />
    </div>
  );
}

Dynamic Components

One of the biggest benefits of useId is when you have components rendered dynamically or multiple times. Since React manages ID generation internally, each instance will have its own unique identity without any extra effort.

function App() {
  return (
    <form>
      <LabeledInput label="First Name" />
      <LabeledInput label="Last Name" />
      {/* Each instance above will have a unique ID generated by React */}
    </form>
  );
}

This prevents JavaScript behavior issues (like selectors targeting the wrong element) and ensures screen readers focus the correct field when the label is clicked.


Generating Multiple IDs at Once

You don't need to call useId for every child element. Use the main ID as a prefix:

function PasswordField() {
  const id = useId();
  
  return (
    <>
      <label htmlFor={id + '-input'}>Password</label>
      <input id={id + '-input'} type="password" aria-describedby={id + '-hint'} />
      <p id={id + '-hint'}>Password must be at least 8 characters long.</p>
    </>
  );
}

Best Practices

  • Use for Accessibility: Always prefer useId over static IDs for linking labels, ARIA descriptions, and titles.
  • Avoid Hardcoded IDs: Fixed IDs in components increase the risk of collisions and hard-to-track bugs.
  • Consistent Usage: Use the hook across your application to maintain a robust dynamic ID pattern.

When NOT to use:

  1. List Keys: Never use useId for the key prop. Use real IDs from your data instead.
  2. CSS Selectors: useId IDs might contain :, requiring escapes in CSS. Using classes for styling is usually better.

Conclusion

useId is a small but powerful tool. It ensures your application is accessible, free of hydration bugs, and future-proof.

If you're still using Math.random(), it's time to update your code!

Reference:


Enjoyed this tip? Share it with anyone still struggling with IDs in React! 🚀