React.js + Next.js · Module 3. Forms and Effects · Lesson 6 · Point 6
The React useId Hook
useId() is a React Hook that creates a unique ID string.
A Hook is a special React function that lets a component use a React feature. Here, the feature is React creating a stable, unique ID for that component.
An ID works like a unique address for one HTML element. The browser can use that address to connect a label to the correct form field:
label htmlFor ↔ field id
This connection is useful because clicking the label focuses the field. It also helps screen readers understand which label describes which field.
useId() does not create an input and it does not submit the form. It only gives us a unique string. We store that string in a variable, then give it to htmlFor and id.
We will first see the problem without useId(). Then we will solve it one small step at a time in a Pizza Palace order form.
Three rules to remember
htmlFor matches id
FormData reads name
List key comes from data
REACT · 01
1. First Understand the Label and Input Connection
Look at this field:
<label htmlFor="customer-name">Customer name</label>
<input id="customer-name" name="customerName" />
The value customer-name is the input's unique address:
htmlFor="customer-name"
↕
id="customer-name"
The two values must match exactly.
When the customer clicks the words Customer name, the browser focuses the input. The user can start typing immediately.
Browser Result
for and id match in the rendered HTML.The label now knows exactly which input belongs to it.
In normal HTML, the label attribute is called for. In React JSX, it is called htmlFor because for is a JavaScript keyword.
REACT · 02
2. See the Problem When a Component Is Reused
A fixed ID works when there is only one copy of the field:
export default function EmailField() {
return (
<div>
<label htmlFor="email">Email</label>
<input id="email" name="email" type="email" />
</div>
);
}
But React components are reusable. We might display the same component twice:
import EmailField from "./components/EmailField";
export default function App() {
return (
<main>
<h2>Delivery customer</h2>
<EmailField />
<h2>Pickup customer</h2>
<EmailField />
</main>
);
}
The browser would receive two inputs with the same ID:
<input id="email" />
<input id="email" />
An ID should be unique on the page. When two elements have id="email", a label may focus the wrong field.
Browser Result
This is the problem that useId() solves.
REACT · 03
3. Create Your First Unique ID with useId
Let us replace the fixed text "email" with an ID created by React.
Step 1 — Import useId
import { useId } from "react";
useId belongs to React, so we import it before using it.
Step 2 — Ask React for an ID
const emailId = useId();
React returns a unique string and we store it in the emailId variable.
The generated string could look similar to :r0:, but its exact format can change. Do not type this value yourself and do not depend on what it looks like.
Step 3 — Use the Same Variable Twice
<label htmlFor={emailId}>Email</label>
<input id={emailId} name="email" type="email" />
The label and input both receive the same generated value.
Complete Component
import { useId } from "react";
export default function EmailField() {
const emailId = useId();
return (
<div>
<label htmlFor={emailId}>Email</label>
<input
id={emailId}
name="email"
type="email"
/>
</div>
);
}
Browser Result
Curly braces mean “use the value stored in this variable”:
htmlFor={emailId}
Quotation marks would use the literal text emailId:
htmlFor="emailId"
These expressions are not the same.
REACT · 04
4. Understand id, htmlFor, and name
These words look similar, but each has one job:
| Attribute | Job | Used By |
|---|---|---|
id |
The field's unique address on the page | Label connection |
htmlFor |
Says which field belongs to the label | Label connection |
name |
The name used for the submitted value | FormData |
Example:
<label htmlFor="pizza-field">Pizza</label>
<input
id="pizza-field"
name="pizzaName"
defaultValue="Margherita"
/>
The label uses the id:
htmlFor="pizza-field" ↔ id="pizza-field"
Remember:
Label connection → id
Submitted value → name
The generated id may be unusual, but the submitted name can stay simple:
const pizzaName = formData.get("pizzaName");
FormData reads name="pizzaName". It does not read the generated ID.
REACT · 05
5. Render the Same Component More Than Once
Now EmailField uses useId(). We can safely render it more than once:
import EmailField from "./components/EmailField";
export default function App() {
return (
<main>
<h2>Delivery customer</h2>
<EmailField />
<h2>Pickup customer</h2>
<EmailField />
</main>
);
}
React calls useId() separately for each component instance:
| Component instance | Illustrative generated ID |
|---|---|
First EmailField |
:r0: |
Second EmailField |
:r1: |
These values are only examples. React decides the real values.
Browser Result
The important idea is not the generated text. The important idea is that every component instance gets a different ID.
REACT · 06
6. Create IDs for More Than One Field
A form can have many fields. Call useId once, then add a different ending for each field.
File: src/components/OrderForm.tsx
import { useId } from "react";
export default function OrderForm() {
const formId = useId();
const customerNameId = formId + "-customer-name";
const emailId = formId + "-email";
return (
<form>
<label htmlFor={customerNameId}>Customer name</label>
<input
id={customerNameId}
name="customerName"
/>
<label htmlFor={emailId}>Email</label>
<input
id={emailId}
name="email"
type="email"
/>
<button type="submit">Continue</button>
</form>
);
}
Browser Result
The first line adds -email to the generated ID:
const emailId = formId + "-email";
A template literal is another way to write the same thing:
const emailId = `${formId}-email`;
Both versions produce an email ID. We use the first version because it is easier to read at the beginning.
Where Can useId Be Called?
Call useId() at the top level of the component, before return:
export default function OrderForm() {
const formId = useId();
return <form>{/* fields */}</form>;
}
Do not call it inside:
- an
ifstatement; - a loop;
- an event handler;
- a
.map()callback.
The simple beginner rule is:
Call
useId()near the top of the component, before thereturnstatement.
useId() accepts no arguments and returns a string.
REACT · 07
7. Build the Complete Pizza Form
Now put the ideas together in one Pizza Palace form.
File: src/components/PizzaOrderForm.tsx
import { useId } from "react";
export default function PizzaOrderForm() {
const formId = useId();
const customerNameId = formId + "-customer-name";
const pizzaNameId = formId + "-pizza-name";
const noteId = formId + "-note";
function handleSubmit(formData: FormData) {
const customerName = formData.get("customerName");
const pizzaName = formData.get("pizzaName");
const note = formData.get("note");
console.log("Customer:", customerName);
console.log("Pizza:", pizzaName);
console.log("Note:", note);
}
return (
<form action={handleSubmit}>
<label htmlFor={customerNameId}>Customer name</label>
<input
id={customerNameId}
name="customerName"
required
/>
<label htmlFor={pizzaNameId}>Pizza</label>
<select id={pizzaNameId} name="pizzaName">
<option value="margherita">Margherita</option>
<option value="pepperoni">Pepperoni</option>
</select>
<label htmlFor={noteId}>Delivery note</label>
<textarea id={noteId} name="note" />
<button type="submit">Place order</button>
</form>
);
}
Browser Result
name lets FormData collect a submitted value.The controls are different, but the ID rule stays the same:
inputfor the customer name;selectfor the pizza;textareafor the delivery note.
useId works with all these controls.
REACT · 08
8. Understand How This Helps People
Accessibility means making a page usable by as many people as possible. It is sometimes shortened to a11y.
Correct labels help:
- screen-reader users understand a field's purpose;
- people focus a small field by clicking its larger label;
- assistive technology associate instructions with controls.
useId helps, but it cannot fix the whole form by itself.
We must still provide:
- meaningful label text;
- suitable input types such as
email; - clear instructions;
- understandable validation messages;
- logical keyboard navigation.
Use clear words and useful error messages as well as correct IDs.
REACT · 09
9. Do Not Use useId as a List Key
An HTML ID and a React key are not the same thing.
| Value | Purpose |
|---|---|
useId() result |
Connect related HTML elements |
React key |
Identify an item in a rendered collection |
Incorrect:
products.map(function (product) {
return <li key={useId()}>{product.name}</li>;
});
This is wrong because useId is inside .map() and because list keys should come from the data.
Use an ID already stored in the data.
interface Pizza {
id: number;
name: string;
}
export default function PizzaList() {
const pizzas: Pizza[] = [
{ id: 1, name: "Margherita" },
{ id: 2, name: "Pepperoni" },
{ id: 3, name: "Vegetariana" },
];
function renderPizza(pizza: Pizza) {
return <li key={pizza.id}>{pizza.name}</li>;
}
return <ul>{pizzas.map(renderPizza)}</ul>;
}
Browser Result
useId.REACT · 10
10. Fix a Label That Does Not Work
If clicking a label does not focus its field, check these five things.
Check 1 — Do htmlFor and id Match?
<label htmlFor={emailId}>Email</label>
<input id={emailId} name="email" />
Both must use the same value.
Check 2 — Is the ID Unique?
Inspect the rendered HTML. Two different elements should not have the same ID.
Check 3 — Is useId at the Top Level?
Move it out of conditions, handlers, loops, and .map() callbacks.
Check 4 — Are You Reading FormData by name?
formData.get("email");
The string matches name="email", not the generated ID.
Check 5 — Are You Using Data IDs for Keys?
Use pizza.id or another stable value from the array item.
Stop when you find the first incorrect value.
REACT · 11
11. Practise One Small Step at a Time
- Create one input and connect its label using
useId. - Add an email field with a second
useIdcall. - Rewrite the form with one base ID and two suffixes.
- Render the same form twice and inspect the generated IDs.
- Add a
selectandtextareato a Pizza Palace form. - Read the submitted values with
FormDataand theirnameattributes. - Explain the difference between
id,name,htmlFor, andkey. - Create a pizza list whose keys come from pizza data.
Do not build everything at once. Test each step before continuing.
REACT · 12
12. Summary — Remember the Three Main Rules
| Concept | Simple Meaning |
|---|---|
useId() |
Generate a unique string for HTML relationships |
id |
Identify one HTML element |
htmlFor |
Connect a label to an element's ID |
name |
Identify a value inside FormData |
key |
Identify one React list item |
When Should I Use useId?
Use useId() when a reusable component needs to connect HTML elements, especially:
- a
labeland aninput; - a field and its help text with
aria-describedby; - a heading and a section with
aria-labelledby.
You do not need useId() for every field. A fixed ID is acceptable when you are certain it appears only once on the page. useId() becomes especially helpful when the same component can appear more than once.
Remember that useId() does not store input values, handle input changes, submit forms, or create list keys.
Final Example
import { useId } from "react";
export default function OrderForm() {
const formId = useId();
const customerNameId = formId + "-customer-name";
const emailId = formId + "-email";
return (
<form>
<label htmlFor={customerNameId}>Customer name</label>
<input
id={customerNameId}
name="customerName"
/>
<label htmlFor={emailId}>Email</label>
<input
id={emailId}
name="email"
type="email"
/>
<button type="submit">Place order</button>
</form>
);
}
Browser Result
The Three Rules
htmlFor and id must match
name is for FormData
key is for React lists
Next lesson: Wait for the next source topic before adding more Module 3 material.