Hirefullstack – Software Engineering & IT-Beratung aus Berlin
← Back to overview
Beginner 7 min read

useId & useDebugValue

Stable ids for forms – and a label for the DevTools

In one sentence

useId produces a unique id that is identical on the server and in the browser. Meant for connecting a label to an input – not for list keys.

In HTML a label and its input belong together through an id: <label htmlFor="mail"> points at <input id="mail">. The snag: with two such fields on one page, clicking either label always lands in the first – the id appears twice.

A random number would be the obvious fix, but it breaks under Server rendering (SSR)The page is produced as finished HTML on the server, so the browser can show something straight away.: the server and the browser roll different numbers, and React complains about a HydrationThe moment React takes over the HTML delivered by the server and brings it to life in the browser.If that HTML does not match what React would produce in the browser, you get a hydration error. mismatch. useId solves both.

field.tsx
function Field({ label }: { label: string }) {
  const id = useId();

  return (
    <>
      <label htmlFor={id}>{label}</label>
      <input id={id} />
    </>
  );
}

// Deriving several ids from one call – this is the intended way:
function SignUp() {
  const id = useId();
  return (
    <>
      <label htmlFor={id + "-mail"}>Email</label>
      <input id={id + "-mail"} />

      <label htmlFor={id + "-pw"}>Password</label>
      <input id={id + "-pw"} aria-describedby={id + "-hint"} />
      <p id={id + "-hint"}>At least 12 characters</p>
    </>
  );
}
Reach for it when …
  • +

    htmlFor / id between a label and a field

  • +

    aria-describedby, aria-labelledby, aria-controls

  • +

    Reusable components that may appear more than once on a page

Skip it when …
  • As a key in lists – for that you need an id from your data

  • As an id for records in a database

  • As a replacement for crypto.randomUUID()

Pitfall Not a key for lists

useId gives one stable value per component instance – in a loop every element would get the same one. Keys have to come from your data.

In more depth: useDebugValue optional

The second hook in this lesson is quickly told: useDebugValue writes a small label next to your own hook in the React DevTools – something like useOnline: "online". It has zero effect on how your app behaves.

debug.ts
function useOnline() {
  const [online, setOnline] = useState(true);
  useDebugValue(online ? "online" : "offline");
  return online;
}

Useful if you are writing a hook library. In ordinary application code you will not need it.

Does it stick?

2 questions on this lesson. Wrong answers show up in your stats.

Now write it yourself

Build an accessible field

The Field component is rendered twice. Right now both instances share one id – clicking the second label focuses the first field. Fix it with useId.

  • Each <input> has its own unique id
  • Each label's htmlFor points at its own field
  • Clicking a label focuses exactly that input
import { useId } from "react";

function Field({ label }: { label: string }) {
  // TODO: create a unique id
  return (
    <p>
      <label htmlFor="field">{label}</label>{" "}
      <input id="field" />
    </p>
  );
}

export default function App() {
  return (
    <div style={{ fontFamily: "system-ui", padding: 16 }}>
      <Field label="First name" />
      <Field label="Last name" />
    </div>
  );
}

Hirefullstack

Need React firepower on your team?

We have been building React and Next.js applications for clients across Germany for years – as a single expert, as reinforcement for an existing team, or as a complete Scrum team.

Talk about your project →