-
Notifications
You must be signed in to change notification settings - Fork 49k
Open
Labels
Status: UnconfirmedA potential issue that we haven't yet confirmed as a bugA potential issue that we haven't yet confirmed as a bug
Description
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
- Create a component that conditionally renders a loop containing
useId()
calls - Server-render the component with the condition initially
false
(default state) - Client hydrates and user interaction changes the condition to
true
- 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:
- Server renders with
showItems = false
→ NouseId()
calls made - Client hydrates with
showItems = false
→ Matches server state initially - User clicks button →
showItems = true
→ 3useId()
calls generate:R1:
,:R2:
,:R3:
- Server HTML has no IDs to match against → Hydration mismatch
The expected behavior
One of the following should happen:
- Stable IDs:
useId()
should generate consistent IDs that don't cause hydration mismatches - Clear error message: React should detect this pattern and provide a specific error about calling
useId()
in loops/conditions - Development warning: React should warn during development when this rule violation occurs
Related Issues:
- Bug: useId not stable on hydration with mid-render state update (React 18) #31653 (useId hydration instability with state updates - similar but different pattern)
- Bug: useId generates invalid ids for SVG elements. #33410 (useId SVG issues - different root cause)
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
sbokhan
Metadata
Metadata
Assignees
Labels
Status: UnconfirmedA potential issue that we haven't yet confirmed as a bugA potential issue that we haven't yet confirmed as a bug