HP2005 (update-before-hydration) means a state or context update reached a
Suspense boundary or root before hydration finished. React then rendered it
on the client instead (errors #421 and #424). Wrap updates that run during
hydration in startTransition.
| Code | HP2005 |
|---|---|
| Name | update-before-hydration |
| Default severity | Warning |
| Group | Problems React reported |
| What it means | A Suspense boundary or root received an update before it finished hydrating (React errors 421/424). |
What HP2005 (update-before-hydration) means
Hydration is not instant: React hydrates the root first and each Suspense boundary when its code and content are ready. If something updates state that a not-yet-hydrated boundary depends on (a context value set in an effect, a parent re-rendering on load), React has to render the new state right away, cannot use the server HTML for it, and renders the boundary on the client instead.
The page keeps working, but that part's server HTML is thrown away and rendered a second time.
The React error it matches
This Suspense boundary received an update before it finished hydrating.React logs this for a boundary; production builds show #421, or #424 when the root itself received the update. See Suspense boundary received an update before it finished hydrating and the minified React error codes.
Likely causes
- A provider that sets state in an effect on load, such as a user or theme read from browser storage, while boundaries below it are still hydrating.
- A parent that re-renders with new props on mount, for example after reading a media query.
How to fix it
- Wrap non-urgent state updates triggered during hydration in
startTransition. For synchronous store updates, keep the changed value out of a boundary that has not hydrated yet.
An update in an effect is urgent by default:
"use client";
import { createContext, useEffect, useState, type ReactNode } from "react";
export const UserContext = createContext<string | null>(null);
export function UserProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<string | null>(null);
useEffect(() => {
setUser(localStorage.getItem("user"));
}, []);
return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
}Marked as a transition, it can wait for the boundaries below to finish hydrating instead of forcing them to render on the client:
"use client";
import {
createContext,
startTransition,
useEffect,
useState,
type ReactNode,
} from "react";
export const UserContext = createContext<string | null>(null);
export function UserProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<string | null>(null);
useEffect(() => {
startTransition(() => {
setUser(localStorage.getItem("user"));
});
}, []);
return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
}Example
⚠ /account 1.4s 1 warning
HP2005 An update arrived before hydration finished
This Suspense boundary received an update before it finished hydrating. This caused the boundary to switch to client re…
Fix: Wrap updates triggered during hydration in startTransition.