How to Update useId Hook in React

The useId hook in React is designed to generate stable, unique identifiers primarily used for accessibility attributes. Because these IDs must remain consistent between server-side rendering (SSR) and client-side hydration, the value returned by useId is immutable and cannot be directly updated. This article explains why useId values are static and details the standard workarounds for generating dynamic, updated IDs in your React components.

Why You Cannot Directly Update useId

The useId hook does not accept any arguments and does not return a setter function like useState does. It returns a single, stable string:

const id = useId();

React guarantees that this ID will remain the same across renders. If React allowed you to directly update or regenerate this ID mid-lifecycle, it would break the HTML hydration process, leading to mismatch errors between the server-rendered markup and the client-rendered virtual DOM.


How to Create Dynamic IDs Using useId

If you need to change or update an ID based on component state, props, or user interaction, you should not try to change the hook itself. Instead, you can combine the stable base ID with dynamic values.

1. Appending Dynamic Suffixes

You can use the stable base ID returned by useId and append dynamic strings or state variables to it. This is highly useful for forms or compound components.

import { useId, useState } from 'react';

function DynamicForm() {
  const baseId = useId();
  const [status, setStatus] = useState('pending');

  // The ID updates implicitly when the status state changes
  const inputId = `${baseId}-${status}`;

  return (
    <div>
      <label htmlFor={inputId}>Submission Status:</label>
      <input id={inputId} type="text" readOnly value={status} />
      <button onClick={() => setStatus('completed')}>Complete</button>
    </div>
  );
}

2. Generating IDs for Dynamic Lists

You cannot call useId inside a loop or conditional statement because of the Rules of Hooks. To generate unique, dynamic IDs for a list of items, generate one base ID and append the item’s index or unique key.

import { useId } from 'react';

function ItemList({ items }) {
  const baseId = useId();

  return (
    <ul>
      {items.map((item, index) => {
        const itemId = `${baseId}-item-${index}`;
        return (
          <li key={item.id} id={itemId}>
            {item.name}
          </li>
        );
      })}
    </ul>
  );
}

Alternative: Using State for Fully Mutable IDs

If your application requires an ID that must change completely upon a user action (and does not rely on SSR matching), you should bypass useId and use standard state management instead.

import { useState } from 'react';

function MutableIdComponent() {
  // Generate a random dynamic ID that can be updated
  const [customId, setCustomId] = useState(() => `id-${Math.random().toString(36).substr(2, 9)}`);

  const regenerateId = () => {
    setCustomId(`id-${Math.random().toString(36).substr(2, 9)}`);
  };

  return (
    <div>
      <p>Current Mutable ID: {customId}</p>
      <button onClick={regenerateId}>Generate New ID</button>
    </div>
  );
}

By understanding that useId is meant for static, hydration-safe identifiers, you can easily handle dynamic ID requirements by appending stateful suffixes or switching to standard state hooks when absolute mutability is required.