This error occurs when a React component inside a Suspense boundary triggers a state update before the boundary completes its initial hydration from server-side rendering. It causes the boundary to abandon streaming and fall back to client-side rendering.
This error is specific to React 18's new Suspense SSR architecture and appears during the hydration phase - when React attaches event handlers to server-rendered HTML to make it interactive. When using Suspense boundaries with server-side rendering, React streams HTML to the browser and then hydrates it progressively. If a component inside a Suspense boundary triggers a state update (via setState, dispatch, or any other mechanism) before that boundary finishes hydrating, React cannot safely merge the server-rendered content with the new state. To prevent inconsistencies, React abandons the streaming hydration for that boundary and switches to full client-side rendering. While the app continues to work, you lose the performance benefits of streaming SSR for that particular boundary.
The primary fix is to mark non-urgent updates using React's startTransition API. This tells React that the update can be deferred until hydration completes:
import { startTransition } from 'react';
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(newData => {
// Wrap the state update in startTransition
startTransition(() => {
setData(newData);
});
});
}, []);
return <div>{data?.value}</div>;
}This allows React to wait until hydration finishes before applying the update.
For more complex scenarios, use the useTransition hook which gives you an isPending flag:
import { useTransition, useState, useEffect } from 'react';
function MyComponent() {
const [data, setData] = useState(null);
const [isPending, startTransition] = useTransition();
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(newData => {
startTransition(() => {
setData(newData);
});
});
}, []);
return (
<div>
{isPending ? 'Loading...' : data?.value}
</div>
);
}Add a condition to prevent data fetching calls during the initial hydration phase:
import { useState, useEffect } from 'react';
function MyComponent() {
const [hasMounted, setHasMounted] = useState(false);
const [data, setData] = useState(null);
useEffect(() => {
setHasMounted(true);
}, []);
useEffect(() => {
// Only fetch after component has mounted
if (!hasMounted) return;
fetch('/api/data')
.then(res => res.json())
.then(setData);
}, [hasMounted]);
return <div>{data?.value}</div>;
}This ensures fetching waits until the client-side mount is complete.
If you're seeing rapid state flips between different values, implement proper cleanup to ignore stale responses:
import { useState, useEffect } from 'react';
function MyComponent({ userId }) {
const [data, setData] = useState(null);
useEffect(() => {
let cancelled = false;
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(newData => {
// Ignore response if effect was cleaned up
if (!cancelled) {
setData(newData);
}
});
return () => {
cancelled = true;
};
}, [userId]);
return <div>{data?.name}</div>;
}This prevents stale requests from causing updates after newer requests have started.
A common cause is data fetching hooks firing before authentication completes. Add guards:
import { useState, useEffect } from 'react';
import { useAuth } from './auth-context';
function MyComponent() {
const { user, isLoading } = useAuth();
const [data, setData] = useState(null);
useEffect(() => {
// Don't fetch until auth is resolved
if (isLoading || !user) return;
fetch('/api/protected-data', {
headers: {
Authorization: `Bearer ${user.token}`
}
})
.then(res => res.json())
.then(setData);
}, [user, isLoading]);
return <div>{data?.value}</div>;
}React 18 Streaming SSR Context: This error is unique to React 18's new Suspense SSR architecture, which allows selective hydration. React streams HTML in chunks and hydrates components progressively, meaning some boundaries may still be hydrating while others are already interactive.
Why React switches to client rendering: When an update occurs during hydration, React cannot guarantee that the server-rendered tree matches what the client would render with the new state. To maintain consistency, it abandons the server content for that boundary and re-renders entirely on the client.
Performance impact: While the app remains functional, switching to client rendering loses the benefits of streaming SSR - users see less content initially and experience longer time-to-interactive for that boundary. Critical performance metrics like First Contentful Paint (FCP) and Largest Contentful Paint (LCP) may be negatively affected.
React team's position: React considers this a recoverable error rather than a critical bug. The team has discussed removing the warning in future versions since the fallback behavior is safe. However, using startTransition remains best practice for optimal performance.
Common library culprits: This error frequently surfaces with data fetching libraries (SWR, React Query, Apollo Client) and state management tools (Redux, Zustand) that trigger updates immediately on mount. Check library-specific documentation for SSR/hydration configurations.
Debugging tip: When the error doesn't specify which boundary or component caused it, add unique keys to your Suspense boundaries and check the React DevTools Profiler to identify which boundaries are re-rendering during hydration.
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite