- React
- Advanced Hooks, Referential Equality, and Stores
- useId: Deterministic Accessible ID Generation
useId: Deterministic Accessible ID Generation
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';23export 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`;89 return (10 <div className="form-group">11 <label htmlFor={inputId}>{label}</label>12 <input13 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:
const id = Math.random().toString(); // ❌ Breaks SSR!- Server generates:
id="0.4829"and sends HTML to client. - Client hydrates: Executes
Math.random(), generatesid="0.9123". - 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
How does useId generate IDs deterministically?
It generates IDs based on the component tree's structural position in the Fiber hierarchy, which is identical on both server and client.
Can useId be used to generate keys for items in a list?
No. Keys must represent stable data identities. useId would break reconciliation when list items are reordered, filtered, or deleted.
How do you link one useId to multiple related elements?
Call useId once to get a base identifier and append suffixes: const id = useId(); const inputId = `${id}-input`; const hintId = `${id}-hint`.
Why do useId output strings contain colons like :r0:?
Colons ensure that the generated IDs are valid in querySelector while avoiding collisions with standard user-defined CSS classes or element IDs.
