A React form component renders three labelled inputs that each need their own id. Do you call useId three times, and how do you derive the ids?
answer
- one call can serve many attributes
- suffix the returned string
- no calling it inside map()
- suffix from the field name, not the index
- the returned format is opaque
basics
~20 sCall useId once and append a suffix per field, such as ${id}-email and ${id}-name. Three separate calls also work, but one call plus suffixes scales to any number of related attributes and keeps the shared prefix visible in the markup.
solid answer
~50 sThe idiomatic answer is one call plus suffixes: `const id = useId()`, then `${id}-email`, `${id}-password`, `${id}-hint`. React returns a unique prefix per component instance, so suffixing stays unique across instances while grouping the whole form under one namespace — which is also easier to read when you inspect the DOM. Calling `useId` three times is perfectly valid and produces three unrelated unique ids; it is just more hook calls for no gain, and it does not scale when you need several attributes per field. What you cannot do is call `useId` inside a `.map()` over the fields, because hooks must be called unconditionally at the top level and the number of calls would vary with the data — if the field list is dynamic, take one id and suffix it with each field's own name or key. The returned string should be treated as opaque: use it as an attribute value, do not parse it or build CSS selectors out of it.
code
javascript · 11 linesimport { useId } from 'react';
export function FieldList({ fields }) {
const id = useId();
return fields.map((field) => (
<div key={field.name}>
<label htmlFor={`${id}-${field.name}`}>{field.label}</label>
<input id={`${id}-${field.name}`} name={field.name} />
</div>
));
}go deeper
Know that one useId call plus a suffix per field gives you all the ids a form needs, and that the hook must be called at the top level of the component.
Explain why the suffix pattern is preferred — one namespace per instance, scales to several attributes per field — and why a dynamic list leaves no alternative.
Choose the suffix deliberately: intrinsic to the field so reordering cannot reassign ids, space-free so list-valued aria attributes stay intact, and never parsed as though the format were specified.
Standardise how components expose ids at all — generate internally and suffix, or accept an id prop from the caller — so composed form libraries do not fight over ownership of the attribute.
## The recommended shape ```jsx import { useId } from 'react'; function SignupForm() { const id = useId(); return ( <form> <label htmlFor={`${id}-name`}>Name</label> <input id={`${id}-name`} /> <label htmlFor={`${id}-email`}>Email</label> <input id={`${id}-email`} aria-describedby={`${id}-email-hint`} /> <p id={`${id}-email-hint`}>Work address preferred.</p> <label htmlFor={`${id}-password`}>Password</label> <input id={`${id}-password`} type="password" /> </form> ); } ``` One hook call, one namespace, as many ids as the component needs. React guarantees the prefix is unique per component instance and stable across server rendering and hydration, so every suffixed string inherits both properties. Render two `SignupForm`s on the same page and their fields cannot collide. ## Why not three calls Three calls are not *wrong*. Each returns its own unique, hydration-stable value and the form works. The reasons to prefer one call are practical: - **It scales past one id per field.** The email input above needs two related ids (itself and its hint). With one prefix that is free; with per-id calls you accumulate a call per attribute. - **The DOM is readable.** Every element in the form shares a visible prefix, which makes it obvious in devtools which fields belong together — useful when the same form appears twice on a page. - **Fewer hook calls to keep in order.** Hooks are matched by call order, so a smaller, flatter set is less to disturb when the component is edited. The React documentation itself presents the suffix pattern as the way to generate several related ids. ## The case where separate calls are not even available A dynamic field list makes multiple calls impossible: ```jsx function FieldList({ fields }) { const id = useId(); return fields.map((field) => ( <div key={field.name}> <label htmlFor={`${id}-${field.name}`}>{field.label}</label> <input id={`${id}-${field.name}`} name={field.name} /> </div> )); } ``` Calling `useId()` inside the `map` callback would mean a variable number of hook calls depending on `fields.length`, which violates the top-level call rule and breaks as soon as the array changes length. Suffixing with a value that is already unique per field — its `name`, or the same value you use as the `key` — gives every input a distinct, stable id from a single call. If a single field genuinely needs its own hook call, the honest fix is to extract it into its own component so the hook is called once per instance at the top level of that component. ## Constraints on the suffix - Pick something stable per field. A field's `name` or record key is good; the array index is bad for the same reason index keys are bad — reorder the fields and the ids shuffle underneath, so a label can end up pointing at a different input than it did before. - Keep the suffix a valid id fragment: no spaces, since a space would split the value where attributes like `aria-describedby` accept a space-separated list. - Do not parse the prefix or build selectors from it. React does not specify the format of the string it returns and has changed it across versions, so treat the whole value as opaque and use it only as an attribute value. If you need to reach an element in a test or in code, query it by its accessible role and name, or hold a ref. ## Interview framing Give the one-call-plus-suffix answer first, acknowledge that multiple calls are legal so it is a style preference in the simple case, then show the dynamic-list case where the suffix approach is the only one available. That progression — convention, reason, hard constraint — is what makes it a considered answer rather than a memorised idiom.
- Why can't you just call useId inside the map callback for a dynamic field list?Because the number of hook calls would then depend on the array length, and hooks must be called unconditionally at the top level so React can match them by call order across renders. Add or remove a field and the calls shift. Suffixing one id, or extracting a per-field component, both avoid it.
- Is suffixing with the array index acceptable?Only for a list that never reorders. Index-based suffixes shuffle when items move, so an element's id changes without its data changing and a label can end up associated with a different input. Use a value intrinsic to the field, such as its name or record id — the same value you would use as the key.
- How should a test find the input rendered with one of these generated ids?By its accessible role and label text, not by the id. The generated prefix is opaque and unstable across React versions, so asserting on it couples the test to an implementation detail. Querying by role and accessible name also verifies the label wiring the id exists to create.
saying these in an interview costs you the question
- Calls useId inside a map over the fields
- Suffixes with the array index in a reorderable list
- Parses the returned id or builds CSS selectors from it
- Thinks calling useId twice can return the same value
- Hard-codes ids and renders the form twice on a page