← Back

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

The label and input are connected because 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

Delivery customer

Pickup customer

The forms look normal, but a duplicated ID can make both labels point to the first email field.

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

React creates the ID, and the same variable connects the label to the email input.

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

Delivery customer

Pickup customer

Each reusable component gets its own ID, so each label focuses its own input.

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

One base ID creates two distinct label-and-field connections.

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 if statement;
  • a loop;
  • an event handler;
  • a .map() callback.

The simple beginner rule is:

Call useId() near the top of the component, before the return statement.

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

Console output

Customer: Nikita

Pizza: margherita

Note: Ring the doorbell

Every label is connected, while each name lets FormData collect a submitted value.

The controls are different, but the ID rule stays the same:

  • input for the customer name;
  • select for the pizza;
  • textarea for 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

Pizza menu
  • Margherita
  • Pepperoni
  • Vegetariana
List keys come from stable pizza data, not from 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

  1. Create one input and connect its label using useId.
  2. Add an email field with a second useId call.
  3. Rewrite the form with one base ID and two suffixes.
  4. Render the same form twice and inspect the generated IDs.
  5. Add a select and textarea to a Pizza Palace form.
  6. Read the submitted values with FormData and their name attributes.
  7. Explain the difference between id, name, htmlFor, and key.
  8. 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 label and an input;
  • 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 final pattern uses one base ID and clear suffixes for related fields.

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.

← Back↑ Back to the top