React Suspense, complete.
ErrorBoundary, Suspense, Delay, and more — so you can focus on the success case.
<ErrorBoundary />
Declarative error handling with fallback, resetKeys, onError, and shouldCatch for selective error catching.
<ErrorBoundary
shouldCatch={NetworkError}
fallback={({ error, reset }) => <ErrorUI error={error} onRetry={reset} />}
>
<App />
</ErrorBoundary>Learn more<Suspense clientOnly />
SSR-safe Suspense boundary. Avoids hydration mismatches in Next.js without dynamic() or useEffect guards.
Learn more<Delay ms={200} />
Prevent flash-of-loading-state. Show spinners only when loading actually takes time. Supports render props for fade-in.
Learn more<DefaultPropsProvider />
Set global default fallbacks for Suspense and Delay. Override per-component when needed.
Learn moreSuspense + ive — everything around React Suspense, in one place.
See it in action
How loading, errors, and recovery actually work.
Without Suspense, a typical TanStack Query page looks like this.
import { useQuery } from '@tanstack/react-query'const Page = () => {const userQuery = useQuery(userQueryOptions())const postsQuery = useQuery({...postsQueryOptions(),select: (posts) => posts.filter(({ isPublic }) => isPublic),})const promotionsQuery = useQuery(promotionsQueryOptions())if (userQuery.isLoading ||postsQuery.isLoading ||promotionsQuery.isLoading) {return 'loading...'}if (userQuery.isError || postsQuery.isError || promotionsQuery.isError) {return 'error'}return (<Fragment><UserProfile {...userQuery.data} />{postsQuery.data.map((post) => (<PostListItem key={post.id} {...post} />))}{promotionsQuery.data.map((promotion) => (<Promotion key={promotion.id} {...promotion} />))}</Fragment>)}
Suspense makes it type-safe. But hooks force you to split components.
import { ErrorBoundary, Suspense } from '@suspensive/react'import { useSuspenseQuery } from '@tanstack/react-query'const Page = () => (<ErrorBoundary fallback="error"><Suspense fallback="loading..."><UserInfo userId={userId} /><PostList userId={userId} /><PromotionList userId={userId} /></Suspense></ErrorBoundary>)const UserInfo = ({ userId }) => {const { data: user } = useSuspenseQuery(userQueryOptions())return <UserProfile {...user} />}const PostList = ({ userId }) => {const { data: posts } = useSuspenseQuery({...postsQueryOptions(),select: (posts) => posts.filter(({ isPublic }) => isPublic),})return posts.map((post) => <PostListItem key={post.id} {...post} />)}const PromotionList = ({ userId }) => {const { data: promotions } = useSuspenseQuery(promotionsQueryOptions())return promotions.map((promotion) => (<PromotionListItem key={promotion.id} {...promotion} />))}
With Suspensive, everything stays at the same depth.
import { ErrorBoundary, Suspense } from '@suspensive/react'import { SuspenseQuery } from '@suspensive/react-query'const Page = () => (<ErrorBoundary fallback="error"><Suspense fallback="loading..."><SuspenseQuery {...userQueryOptions()}>{({ data: user }) => <UserProfile {...user} />}</SuspenseQuery><SuspenseQuery{...postsQueryOptions()}select={(posts) => posts.filter(({ isPublic }) => isPublic)}>{({ data: posts }) =>posts.map((post) => <PostListItem key={post.id} {...post} />)}</SuspenseQuery><SuspenseQuery{...promotionsQueryOptions()}select={(promotions) => promotions.filter(({ isPublic }) => isPublic)}>{({ data: promotions }) =>promotions.map((promotion) => (<PromotionListItem key={promotion.id} {...promotion} />))}</SuspenseQuery></Suspense></ErrorBoundary>)
Already using react-error-boundary?
You’re on the right track.
react-error-boundary is a great library that made declarative error handling practical in React. Suspensive’s ErrorBoundary adds a few things we found useful in production — shouldCatch for catching only specific errors, ErrorBoundaryGroup for resetting multiple boundaries at once, and safe fallback error propagation. We also needed to solve loading states, SSR hydration, flash-of-loading UX, and data fetching — so we built those too, all in one package.
| Suspensive | react-error-boundary | @sentry/react | DIY (Class Component) | ||
|---|---|---|---|---|---|
| Error Boundary | shouldCatch | ✓ | ✗ | ✗ | ✗ |
| ErrorBoundaryGroup | ✓ | ✗ | ✗ | ✗ | |
| useErrorBoundaryFallbackProps | ✓ | ✗ | ✗ | ✗ | |
| Safe fallback error propagation | ✓ To parent | ✗ Recursive | ✗ Recursive | ✗ | |
| TypeScript error type inference | ✓ Via shouldCatch | ✗ | ✗ | ✗ | |
| useErrorBoundary hook | ✓ | ✓ | ✗ | ✗ | |
| Fallback UI with error & reset | ✓ | ✓ | ✓ | ✗ | |
| resetKeys | ✓ | ✓ | ✗ | ✗ | |
| onReset callback | ✓ | ✓ | ✓ | ✗ | |
| onError callback | ✓ | ✓ | ✓ | ✗ | |
| HOC support | ✓ ErrorBoundary.with | ✓ withErrorBoundary | ✓ withErrorBoundary | ✗ | |
| Declarative API | ✓ | ✓ | ✓ | ✗ | |
| Async Rendering | SSR-safe Suspense (clientOnly) | ✓ | ✗ | ✗ | ✗ |
| Flash-of-loading prevention (Delay) | ✓ | ✗ | ✗ | ✗ | |
| Global default fallbacks (DefaultPropsProvider) | ✓ | ✗ | ✗ | ✗ | |
| Declarative data fetching (SuspenseQuery) | ✓ | ✗ | ✗ | ✗ | |
| Client-only rendering (ClientOnly) | ✓ | ✗ | ✗ | ✗ |
Playground
Edit the code and see Suspensive in action.
Errors are intentional — try Retry to see how ErrorBoundary recovers.
zero config, incremental adoption, works with Next.js and any React app.