React’s useId hook offers an accessible solution for managing unique identifiers in components that are consisten between server and client.
In React version 18, React introduced a new hook titled useId that provides a very helpful solution for managing unique identifiers within components. In this article, we’ll spend some time exploring its functionality, use cases and benefits in creating more accessible and maintainable React applications.
In a nutshell, the useId
hook generates a unique and stable ID for components. This ID remains consistent across server and client renders, ensuring that server-side generated HTML matches the client-side generated HTML. This is particularly useful for accessibility concerns like associating form inputs with labels or in any scenario where unique identifiers are needed within a component.
Let’s walk through an example to illustrate the use of the useId
hook in React. We’ll start with a simple component, say, a form component with an input field and a label.
import React from "react";
export function Form() {
return (
<div>
<label htmlFor="nameInput">Name:</label>
<input id="nameInput" type="text" />
</div>
);
}
In the above code example, we’re hardcoding the id
for the input and the corresponding htmlFor
attribute for the label. This works fine for a single instance of FormComponent
, but if we were to use multiple instances of this component on the same page, we’d encounter duplicate ID issues, which isn’t ideal for accessibility and can cause HTML validation issues.
If we were to use multiple instances of the above Form
component on the same page, we would want to dynamically generate a unique ID for each instance of the input field and its associated label. This is crucial when we have multiple instances of the component, as each input-label pair needs a unique identifier.
To achieve this goal, we’ll refactor the Form
component to use the useId
hook to generate a unique ID:
import React, { useId } from "react";
export function Form() {
// Generate a unique ID
const inputId = useId();
return (
<div>
<label htmlFor={inputId}>Name:</label>
<input id={inputId} type="text" />
</div>
);
}
In this revised version, the useId
hook generates a unique ID, stored in a variable named inputId
, which we then use for the id
attribute of the input and the htmlFor
attribute of the label.
With these recent changes, suppose we have a parent App
component that renders multiple Form
components.
import React from "react";
import { Form } from "./Form";
function App() {
return (
<div>
<Form />
<Form />
<Form />
</div>
);
}
When React renders these components, each Form
instance calls useId
, which generates a unique ID. The first instance might generate an ID like "input-0"
, the second "input-1"
and the third "input-2"
. The actual values depend on React’s internal mechanism and may vary. For example, when testing the above example, we notice unique IDs like the following:
See the above running code example in this CodeSandbox link.
At any point in time, if we need to add custom prefixes within the component to further distinguish the IDs generated by the useId
hook, we can create a simple wrapper function around useId
. This approach can be useful in large applications where different modules or components might need more specific ID management to avoid potential clashes, even with the unique IDs generated by useId
.
Here’s an example of how we could implement a custom prefix within the component by creating a custom usePrefixedId
hook:
import React, { useId } from "react";
// A wrapper function to add a custom prefix to the ID
function usePrefixedId(prefix) {
const id = useId();
return `${prefix}-${id}`;
}
export function Form({ prefix }) {
// Use the custom hook to generate a prefixed ID
const inputId = usePrefixedId(prefix);
return (
<div>
<label htmlFor={inputId}>Name:</label>
<input id={inputId} type="text" />
</div>
);
}
In the above example, the custom usePrefixedId
hook takes a prefix
and appends it to the ID generated by useId
. When using the Form
component, we can optionally pass a prefix
prop to customize the ID. If no prefix
is provided, it defaults to 'form'
.
In the parent App
component instance we had earlier, we can specify different prefixes for each component like the following:
import React from "react";
import { Form } from "./Form";
function App() {
return (
<div>
<Form prefix="user" />
<Form prefix="profile" />
<Form prefix="contact" />
</div>
);
}
The IDs for each Form
component will not just be unique across instances but also clearly associated with a specific context or part of the application (e.g., "user-input-0"
, "profile-input-1"
and "contact-input-2"
). The actual values still depend on React’s internal ID generation mechanism but will now include the specified prefixes, adding an additional layer of clarity and distinction.
See the above running code example in this CodeSandbox link.
Lastly, the React documentation tells us we’re also able to customize the prefix of all identifiers generated by the useId
hook by using the identifierPrefix
option in the createRoot call that is used to initialize the React application:
import { createRoot } from "react-dom/client";
import App from "./App";
const rootElement = document.getElementById("root", {
// Set a custom prefix for identifiers generated by useId
identifierPrefix: "fresh-app-",
});
const root = createRoot(rootElement);
root.render(<App />);
Specifying a global prefix like the above is often helpful when rendering multiple React applications on a single page. It helps to avoid ID conflicts between components from different applications. Each application can have its own unique prefix, so that IDs generated by useId
are unique not only within each application but across all applications on the page.
In summary, React’s useId
hook offers an accessible solution for managing unique identifiers in components. Dynamically generating stable and consistent IDs helps to keep the identifiers consistent across server and client renders. This consistency is crucial for applications that rely on server-side rendering, as it guarantees that the server-rendered HTML matches the client-rendered HTML.
For more details on the useId
hook, be sure to read through the official React documentation.
Hassan is a senior frontend engineer and has helped build large production applications at-scale at organizations like Doordash, Instacart and Shopify. Hassan is also a published author and course instructor where he’s helped thousands of students learn in-depth frontend engineering skills like React, Vue, TypeScript, and GraphQL.