Lesson 55 of 80
A hook that exists to solve one specific problem: generating an id that's stable across server and client render, for the sole purpose of wiring up accessibility attributes.
10 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Form fields and their labels, or an input and its error message, need to reference each other by id — <label htmlFor="email"> needs an <input id="email"> to point at. The obvious move is Math.random() or a counter, but in an app that renders on the server and then hydrates on the client, that breaks: the server generates one id, the client generates a different one on its first render pass, and React's hydration check flags a mismatch (or, worse, doesn't and you get two different ids silently disagreeing).
useId solves exactly this. It returns a unique string that's guaranteed to match between the server-rendered HTML and the client's hydration pass, because React computes it from the component's actual position in the tree rather than from an incrementing counter or randomness. Call it once per component that needs an id, and use that value everywhere you need to link elements together.
import { useId } from "react";
function EmailField({ error }) {
const id = useId();
const errorId = `${id}-error`;
return (
<div>
<label htmlFor={id}>Email</label>
<input
id={id}
type="email"
aria-describedby={error ? errorId : undefined}
aria-invalid={!!error}
/>
{error && (
<p id={errorId} role="alert">
{error}
</p>
)}
</div>
);
}
Render <EmailField /> twice on the same page and each instance gets its own distinct id (something like :r0: and :r1:), so htmlFor/id pairs never collide — you don't have to thread a manually-chosen prefix through every reusable field component to avoid duplicate ids when the same component appears more than once.
useId is not for React key props. Keys need to be stable per data item across re-renders (usually a database id or similarly durable value from your data), while useId generates a fresh id per component instance tied to render position — using it for list keys defeats the purpose and can cause the exact reconciliation bugs stable keys are meant to prevent.:r0: aren't meant to be readable or to double as CSS selectors or test hooks — they're intentionally opaque so nobody's tempted to depend on their format. Use data-testid for test selectors, not a useId value.The entire reason this hook needs to exist — rather than everyone just writing their own counter or crypto.randomUUID() — is the server/client hydration mismatch problem: React renders once on the server and once again on the client, and for hydration to succeed those two passes have to produce identical output. useId is a narrow, purpose-built fix for the one place that mismatch commonly bites real apps — accessibility attributes that need matching ids.