This error occurs when you wrap components in a Suspense boundary but none of the child components actually suspend (throw a Promise). Suspense requires at least one async operation like lazy loading, data fetching with a Suspense-compatible library, or the use() hook in React 19. Understanding how Suspense works and when components properly suspend resolves this issue.
React Suspense is designed to handle asynchronous operations by displaying a fallback UI while waiting for content to load. However, Suspense only activates when a child component "suspends" by throwing a Promise during rendering. If you wrap regular synchronous components in a Suspense boundary without any actual async operations, React may warn or fail because there is nothing to suspend. The error indicates that your Suspense boundary contains only components that render synchronously without triggering the suspension mechanism. Suspense is not just a loading state wrapper—it requires specific integration with async patterns like React.lazy(), data-fetching libraries that support Suspense, or React 19's use() hook.
The most common and straightforward use of Suspense is with React.lazy() for code splitting. Lazy-loaded components automatically suspend while their code is being fetched. Wrap the lazy component in a Suspense boundary with a fallback.
import { Suspense, lazy } from "react";
// Lazy load the component - this makes it suspend
const HeavyComponent = lazy(() => import("./HeavyComponent"));
export function App() {
return (
<Suspense fallback={<div>Loading component...</div>}>
<HeavyComponent />
</Suspense>
);
}React 19 introduced the use() hook which allows you to read Promises directly in render. When you call use() with a Promise, the component suspends until the Promise resolves. This is the official way to integrate Suspense with async data.
import { Suspense, use } from "react";
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
// Calling use() suspends the component until userPromise resolves
const user = use(userPromise);
return <div>Welcome, {user.name}</div>;
}
export function App() {
const userPromise = fetchUser();
return (
<Suspense fallback={<div>Loading user...</div>}>
<UserProfile userPromise={userPromise} />
</Suspense>
);
}Note: The use() hook is only available in React 19+.
Libraries like React Query, SWR, and Relay support Suspense mode. Enable Suspense support in their configuration, and they will automatically throw Promises when data is loading, triggering your Suspense fallback.
import { Suspense } from "react";
import { useSuspenseQuery } from "@tanstack/react-query";
function UserData() {
// useSuspenseQuery suspends while fetching
const { data } = useSuspenseQuery({
queryKey: ["user"],
queryFn: fetchUser,
});
return <div>{data.name}</div>;
}
export function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<UserData />
</Suspense>
);
}For SWR:
import useSWR from "swr";
function UserData() {
const { data } = useSWR("/api/user", fetcher, { suspense: true });
return <div>{data.name}</div>;
}Suspense only handles loading states. To handle errors in async components, wrap your Suspense boundary with an Error Boundary. This ensures errors during loading do not break your app.
import { Suspense, Component } from "react";
class ErrorBoundary extends Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <div>Something went wrong</div>;
}
return this.props.children;
}
}
export function App() {
return (
<ErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</Suspense>
</ErrorBoundary>
);
}Or use a library like react-error-boundary:
import { ErrorBoundary } from "react-error-boundary";
<ErrorBoundary fallback={<div>Error occurred</div>}>
<Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</Suspense>
</ErrorBoundary>Traditional data fetching with useEffect and useState does not trigger Suspense. If you are using this pattern, either migrate to a Suspense-compatible library or remove the Suspense boundary and handle loading states manually.
// WRONG - This does NOT suspend
function UserData() {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser().then(setUser);
}, []);
if (!user) return <div>Loading...</div>;
return <div>{user.name}</div>;
}
// Wrapping this in Suspense does nothing - remove Suspense or migrate to use()Ensure the libraries you use actually support Suspense. Older versions of React Query, SWR, Apollo Client, and other data-fetching libraries may not support Suspense mode.
For React Query, use @tanstack/react-query v4+ and useSuspenseQuery.
For SWR, enable { suspense: true } in configuration.
For Apollo Client, use the @apollo/client v3.8+ with Suspense hooks.
Check library documentation for the correct Suspense integration method.
In Next.js 13+ with App Router, server components can use async/await directly and integrate seamlessly with Suspense. This is the recommended approach for server-side data fetching with Suspense.
// app/page.tsx (Next.js App Router)
import { Suspense } from "react";
async function UserData() {
const user = await fetchUser();
return <div>{user.name}</div>;
}
export default function Page() {
return (
<Suspense fallback={<div>Loading...</div>}>
<UserData />
</Suspense>
);
}Server components that use async/await automatically suspend when used with Suspense.
Understanding how Suspense works internally helps debug issues. Suspense listens for components that throw Promises during rendering. When a Promise is thrown, React catches it, renders the fallback UI, and waits for the Promise to resolve. Once resolved, React re-renders the component. This "throw and catch" mechanism is why regular async/await functions do not work—they return Promises instead of throwing them. The use() hook in React 19 bridges this gap by handling the Promise throw internally. For React 18 and earlier, you must use libraries that implement this pattern or use React.lazy(). In production applications, consider using frameworks like Next.js that provide first-class Suspense support with Server Components and streaming SSR. Be aware that Suspense is still evolving, and some edge cases with nested Suspense boundaries, concurrent updates, and transitions may behave unexpectedly. Always test Suspense behavior in production builds, as development mode may behave differently. For fine-grained control over loading states, consider using the useTransition() hook alongside Suspense to prevent UI "jank" when navigating between Suspense boundaries.
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