这是indexloc提供的服务,不要输入任何密码
Skip to content

Bug: useId() causes hydration mismatch when called inside conditionally rendered loops during SSR #33779

@AjayKumbham

Description

@AjayKumbham

Bug Description:
useId() causes hydration mismatch errors when called inside loops that are conditionally rendered during SSR. The server renders with different conditional states than the client during hydration, causing ID sequence misalignment and hydration warnings.

React version:

  • React: 18.2.0
  • React DOM: 18.2.0

Steps To Reproduce

  1. Create a component that conditionally renders a loop containing useId() calls
  2. Server-render the component with the condition initially false (default state)
  3. Client hydrates and user interaction changes the condition to true
  4. Observe hydration mismatch warnings in console

Minimal reproduction code:

import React, { useState } from 'react';

export default function App() {
  const [showItems, setShowItems] = useState(false);
  
  return (
    <div>
      <h1>useId Hydration Bug Demo</h1>
      
      {/* This pattern causes hydration mismatch */}
      {showItems && (
        <div>
          {new Array(3).fill(0).map((_, index) => (
            <div key={index} id={useId()}>
              Item {index + 1}
            </div>
          ))}
        </div>
      )}
      
      <button onClick={() => setShowItems(true)}>
        Show Items (triggers hydration mismatch)
      </button>
    </div>
  );
}

SSR setup for reproduction:

// server.js
import { renderToString } from 'react-dom/server';
import App from './App';

// Server renders with showItems = false (no useId calls)
const serverHTML = renderToString(<App />);

// client.js
import { hydrateRoot } from 'react-dom/client';
import App from './App';

// Client hydrates, then user clicks button
// showItems becomes true, causing 3 useId() calls
hydrateRoot(document.getElementById('root'), <App />);

The current behavior

When the button is clicked and showItems becomes true, the following hydration mismatch errors occur:

Warning: Prop `id` did not match. Server: "" Client: ":R1:"
    at div
    at div
    at App

Warning: Prop `id` did not match. Server: "" Client: ":R2:"
    at div
    at div
    at App

Warning: Prop `id` did not match. Server: "" Client: ":R3:"
    at div
    at div
    at App

Technical analysis:

  1. Server renders with showItems = false → No useId() calls made
  2. Client hydrates with showItems = false → Matches server state initially
  3. User clicks button → showItems = true → 3 useId() calls generate :R1:, :R2:, :R3:
  4. Server HTML has no IDs to match against → Hydration mismatch

The expected behavior

One of the following should happen:

  1. Stable IDs: useId() should generate consistent IDs that don't cause hydration mismatches
  2. Clear error message: React should detect this pattern and provide a specific error about calling useId() in loops/conditions
  3. Development warning: React should warn during development when this rule violation occurs

Related Issues:

Working fix:

// ✅ Extract component to avoid useId in loop
function ItemWithId({ children }) {
  const id = useId();
  return <div id={id}>{children}</div>;
}

export default function App() {
  const [showItems, setShowItems] = useState(false);
  
  return (
    <div>
      {showItems && (
        <div>
          {new Array(3).fill(0).map((_, index) => (
            <ItemWithId key={index}>
              Item {index + 1}
            </ItemWithId>
          ))}
        </div>
      )}
      <button onClick={() => setShowItems(true)}>Show Items</button>
    </div>
  );
}

Environment:

  • Node.js: 18.17.0
  • Browser: Chrome 119.0.6045.105
  • SSR: Custom React SSR setup
  • Affects: Next.js, Remix, Gatsby, and other SSR frameworks

Impact:
This bug is particularly problematic because:

  • Only manifests during SSR/hydration (works fine in client-only mode)
  • Easy to accidentally introduce when refactoring conditional rendering
  • Error message is unclear about the root cause (shows ID mismatch, not rule violation)
  • Combines multiple React concepts in an unexpected way

Metadata

Metadata

Assignees

No one assigned

    Labels

    Status: UnconfirmedA potential issue that we haven't yet confirmed as a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions