React Curriculum

useId: Deterministic Accessible ID Generation

Easy100% Free~20 mins#react#useid#accessibility#ssr#hydration#aria
Interview Answer (30-Second Summary)

useId generates unique, deterministic identifiers that remain identical across server rendering and client hydration. It avoids hydration mismatches caused by Math.random() and powers accessible form elements (htmlFor, aria-describedby).

Key Learning Objectives
✓

Explain why Math.random() or global counters break hydration in SSR applications.

✓

Understand how useId generates deterministic tree-position-based IDs (:r0:, :r1:).

✓

Link HTML form labels, inputs, and aria-describedby hints accessibly using useId.

✓

Know when NOT to use useId (never as list keys in array mapping).

The Interview Problem & Context

Why does generating IDs using Math.random() cause hydration mismatch errors in SSR? How does useId solve this problem, and why must useId never be used to generate list keys?

1import { useId } from 'react';
2
3export function AccessibleInputField({ label, hint }: { label: string; hint?: string }) {
4 // Generates deterministic base ID: e.g. ":r0:"
5 const id = useId();
6 const inputId = `${id}-input`;
7 const hintId = `${id}-hint`;
8
9 return (
10 <div className="form-group">
11 <label htmlFor={inputId}>{label}</label>
12 <input
13 id={inputId}
14 aria-describedby={hint ? hintId : undefined}
15 />
16 {hint && <span id={hintId} className="hint">{hint}</span>}
17 </div>
18 );
19}
Deep Architectural Explanation

Why useId Was Introduced in React 18

The SSR Hydration Problem

In accessible HTML, inputs require unique IDs to connect with <label htmlFor="..."> and aria-describedby.
Prior to React 18, developers often used:

ts
const id = Math.random().toString(); // ❌ Breaks SSR!
  1. Server generates: id="0.4829" and sends HTML to client.
  2. Client hydrates: Executes Math.random(), generates id="0.9123".
  3. Hydration Mismatch Error: React detects that server HTML attributes do not match client DOM attributes, throwing an error and discarding server markup!

The useId Deterministic Solution

useId does not use random numbers or global sequential counters. Instead, it generates IDs based on the component's exact position in the React Fiber tree hierarchy:

  • Result: :r0:, :r1:, etc.
  • Because the component tree hierarchy is identical on the server and client, the generated ID is 100% deterministic and guaranteed to match.

Golden Rule: Never Use useId for Keys!

useId is designed for HTML element attributes (id, aria-labelledby). Never use useId to generate keys for .map(). Keys must be generated from your data records (item.id).

Common Interview Traps & Pitfalls
✕

Using useId to generate keys in array mapping ({items.map(item => <li key={useId()}>)}).

✕

Using Math.random() in SSR applications for input IDs.

✕

Calling useId multiple times in the same component instead of generating one base ID with suffixes.

Senior Interviewer Follow-Up Probes
?

How does useId handle multi-root React 18 applications using identifierPrefix?

?

Why does useId use colons (:r0:) in its generated string format?

?

How does aria-describedby enhance form accessibility for screen readers?

Frequently Asked Interview Questions
Why does Math.random() break SSR hydration when used for element IDs?

Because the server generates a different random number than the client, causing a hydration mismatch where React discovers mismatched DOM attributes.

It generates IDs based on the component tree's structural position in the Fiber hierarchy, which is identical on both server and client.

No. Keys must represent stable data identities. useId would break reconciliation when list items are reordered, filtered, or deleted.

Call useId once to get a base identifier and append suffixes: const id = useId(); const inputId = `${id}-input`; const hintId = `${id}-hint`.

Colons ensure that the generated IDs are valid in querySelector while avoiding collisions with standard user-defined CSS classes or element IDs.