본문 바로가기
Dev/React

A Practical Guide to useId in React 19

by ArcticBear 2025. 3. 8.
반응형



 1. Introduction

When building React applications, you often need unique identifiers for DOM elements—especially for accessibility (like associating labels with form controls) or any feature that relies on distinct element IDs. Before React 18, we typically generated IDs using external libraries or ad-hoc solutions that didn’t always play nicely with server-side rendering (SSR).

With the addition of useId, React provides a first-party hook to generate unique and stable IDs across client and server. In React 19, this hook retains the same signature and behavior, providing a reliable way to handle identifiers.

 

 2. What Is useId?

useId is a React hook that returns a unique string. You can safely use this string as a DOM element’s id attribute or anywhere a stable identifier is needed. It is specifically designed to:

  1. Work with SSR: Ensures IDs match between server and client.
  2. Prevent Collisions: Each call to useId returns a distinct value, preventing accidental duplicates.
  3. Remain Stable: Once generated, an ID won’t suddenly change across renders.

Basic Signature

const uniqueId = useId();

Returns: A string (e.g., "r:abcd123") that you can use directly as an id or as a prefix for multiple related IDs.

 

 

 3. Common Use Cases

 

  • Form Accessibility
    • Associate a <label> with its corresponding form element (<input>, <select>, etc.).
    • Example: <label htmlFor={uniqueId}>Email:</label> and <input id={uniqueId} ... />
  • Dynamic Lists or Components
    • When rendering lists of interactive components, each might need a stable id for internal references or accessibility hooks (e.g., ARIA attributes).
  • SSR Consistency
    • In server-rendered apps, you want to avoid hydration mismatches caused by client-only ID generation. useId ensures the same ID is used on both server and client.
  • Complex Components
    • If a component has multiple sub-elements requiring unique IDs (e.g., a tab system or a modal with ARIA attributes), you can generate one main id and derive sub-IDs from it.

 

 

 4. Basic Example

Let’s say you have a form that needs a label and an input:

 

import React, { useId } from "react";

function EmailField() {
  const id = useId();

  return (
    <div>
      <label htmlFor={id}>Email:</label>
      <input id={id} type="email" />
    </div>
  );
}

export default EmailField;

How It Works:

  • useId returns a unique string.
  • We use that string for both htmlFor (label) and id (input) so the label is properly associated with the input.

 

 5. Advanced Patterns

 

5.1 Multiple Related IDs

Sometimes you have multiple elements that each need a unique ID, but they should share a common prefix. You can concatenate the returned ID with suffixes:

import React, { useId } from "react";

function TabPanel() {
  const baseId = useId();
  const tabId = `${baseId}-tab`;
  const panelId = `${baseId}-panel`;

  return (
    <div>
      <button id={tabId} aria-controls={panelId}>
        Open Panel
      </button>
      <div id={panelId} role="region" aria-labelledby={tabId}>
        Panel Content
      </div>
    </div>
  );
}

export default TabPanel;

This approach keeps IDs namespaced under the same root, reducing the chance of collisions and making it clearer that these elements are related.

 

5.2 SSR Example

In an SSR environment (e.g., using Next.js or a custom Node.js server), React will generate IDs that match between server-rendered HTML and client-side React:

  1. Server: Renders the components, calling useId() to create IDs.
  2. Client: Hydrates the same components, ensuring the identical IDs are reused.

This prevents hydration warnings or mismatches that can occur if you rely on random ID generators in the client only.

 

5.3 Combining with Other Hooks

useId can be used alongside any other hooks. For instance, you might combine it with useState or useContext:

function CustomToggle() {
  const id = useId();
  const [on, setOn] = React.useState(false);

  return (
    <div>
      <button
        id={`${id}-toggle`}
        aria-pressed={on}
        onClick={() => setOn(!on)}
      >
        {on ? "Turn Off" : "Turn On"}
      </button>
    </div>
  );
}

 

 6. Best Practices

 

  • Generate Once, Use Everywhere
    • Call useId() once in a component and reuse that value for multiple elements if they’re logically related.
  • Avoid Overcalling
    • You only need one useId() per unique prefix. For multiple sub-elements, simply concatenate the base ID with different suffixes.
  • Server and Client Consistency
    • Rely on useId instead of client-only ID generation to avoid SSR mismatches.
  • Accessibility
    • Make sure you’re using the IDs to properly label or describe interactive elements, especially with ARIA attributes.
  • Name Clashes
    • If you manually craft suffixes, ensure they don’t coincide with IDs from other components. The “base ID” portion will be unique, but suffix collisions can happen if you reuse the same suffix too broadly.

 

 

 7. Pitfalls & Caveats

  1. Not for Arrays’ key Prop
    • useId is not recommended for keys in lists—keys help React identify DOM elements across renders, which differs from the purpose of an element’s id in the HTML. Rely on stable unique data (like an item’s ID) for your list keys.
  2. Client-Only Libraries
    • If you rely on a library that inserts IDs only in the client, you can run into SSR mismatches. Whenever possible, use useId for consistency.
  3. Overusing
    • You don’t need a new ID for every single element—only for those that require a stable, unique reference, especially for accessibility or bridging to external libraries that rely on IDs.
  4. Unsupported in Old React Versions
    • If you’re not on React 18 or 19, useId won’t be available. You might need a polyfill or alternative approach for older React apps.

 

 8. Example: Accessible Dropdown

Below is a more complete example showing how useId can help create an accessible dropdown list:

import React, { useState, useId } from "react";

function Dropdown({ label, options }) {
  const baseId = useId();
  const labelId = `${baseId}-label`;
  const selectId = `${baseId}-select`;

  const [value, setValue] = useState(options[0]);

  return (
    <div>
      <label id={labelId} htmlFor={selectId}>
        {label}
      </label>
      <select
        id={selectId}
        aria-labelledby={labelId}
        value={value}
        onChange={(e) => setValue(e.target.value)}
      >
        {options.map((opt) => (
          <option key={opt} value={opt}>
            {opt}
          </option>
        ))}
      </select>
      <p>Selected: {value}</p>
    </div>
  );
}

export default function App() {
  return (
    <div>
      <h1>useId Example</h1>
      <Dropdown
        label="Choose a fruit:"
        options={["Apple", "Banana", "Cherry"]}
      />
    </div>
  );
}

Why useId Helps

  • It guarantees a consistent ID for both the label and select.
  • The user can navigate easily with screen readers, which rely on matching htmlFor or ARIA attributes to id.

 

 9. Conclusion

useId might seem like a small addition, but it solves a recurring need in React for stable, unique IDs—particularly in SSR or accessibility-heavy contexts. Whether you’re building forms, dynamic lists, or complex interactive components, useId ensures your IDs are consistently generated on both server and client, reducing potential mismatches or random collisions.

Key Takeaways

  • Use It for Accessibility: Helps label or associate elements without manual ID collisions.
  • SSR Compatibility: Ensures consistent IDs across server and client.
  • Stable Over Time: The ID doesn’t change across renders, so references remain intact.
  • Limitations: Not meant for list keys or random ID strings that must be different every mount.

By embracing useId in React 19, you’ll streamline ID management, especially if your app depends on cross-render consistency and accessibility. Give it a try next time you’re tying together form labels, buttons, or ARIA attributes in your project!

 

 

 

반응형