error-handling
Layered error handling with @suspensive/react: ErrorBoundary (fallback, shouldCatch, resetKeys, onReset, onError), ErrorBoundary.Consumer, useErrorBoundary().setError, useErrorBoundaryFallbackProps, ErrorBoundaryGroup, ErrorBoundaryGroup.Consumer, blockOutside, useErrorBoundaryGroup, ErrorBoundaryFallbackProps type. Load when catching, filtering, resetting, or propagating render errors.
Error handling with ErrorBoundary and ErrorBoundaryGroup
This skill builds on react. Read ../SKILL.md first for the client-only constraint, the .with() HOC pattern, and removed legacy APIs.
Setup
'use client'
import { ErrorBoundary } from '@suspensive/react'
import { UserProfile } from './UserProfile'
export const UserSection = () => (
<ErrorBoundary
fallback={({ error, reset }) => (
<div>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
)}
onError={(error, info) => console.error(error, info)}
onReset={() => console.log('reset')}
>
<UserProfile />
</ErrorBoundary>
)fallback is the single fallback prop (element or ({ error, reset }) => ReactNode). Type a standalone fallback component with ErrorBoundaryFallbackProps.
Core Patterns
Catch only one error type, let the rest propagate
'use client'
import { ErrorBoundary } from '@suspensive/react'
import { NetworkError } from './errors'
import { Feed } from './Feed'
export const Example = () => (
<ErrorBoundary fallback={({ error }) => <p>Unexpected: {error.message}</p>}>
<ErrorBoundary
shouldCatch={NetworkError}
fallback={({ error, reset }) => <button onClick={reset}>Network failed, retry: {error.message}</button>}
>
<Feed />
</ErrorBoundary>
</ErrorBoundary>
)shouldCatch accepts an ErrorConstructor, a callback (error) => boolean, a boolean, or an array mixing all three; uncaught errors propagate to the parent boundary.
Reset every boundary on the page at once
'use client'
import { ErrorBoundary, ErrorBoundaryGroup } from '@suspensive/react'
import { StatsSection, UserSection } from './sections'
export const Dashboard = () => (
<ErrorBoundaryGroup>
<ErrorBoundaryGroup.Consumer>
{(group) => <button onClick={group.reset}>Reset all</button>}
</ErrorBoundaryGroup.Consumer>
<ErrorBoundary fallback={({ error }) => <p>{error.message}</p>}>
<UserSection />
</ErrorBoundary>
<ErrorBoundary fallback={({ error }) => <p>{error.message}</p>}>
<StatsSection />
</ErrorBoundary>
</ErrorBoundaryGroup>
)Nested groups are also reset by the parent group unless they set blockOutside; useErrorBoundaryGroup().reset does the same from any child component.
Trigger the boundary without throwing during render
'use client'
import { ErrorBoundary, useErrorBoundary } from '@suspensive/react'
import { useEffect } from 'react'
import { fetchSomething } from './api'
const SetErrorAfterFetch = () => {
const errorBoundary = useErrorBoundary()
useEffect(() => {
fetchSomething().then(
(response) => {},
(error) => errorBoundary.setError(error)
)
}, [])
return <>No error</>
}
export const Example = () => (
<ErrorBoundary fallback={({ error }) => <p>{error.message}</p>}>
<SetErrorAfterFetch />
</ErrorBoundary>
)setError routes async (event/effect) errors — which React error boundaries cannot catch via throw — into the nearest ErrorBoundary; ErrorBoundary.Consumer exposes the same object inline in JSX.
Read error and reset inside a fallback without prop drilling
'use client'
import { ErrorBoundary, useErrorBoundaryFallbackProps } from '@suspensive/react'
import { Feed } from './Feed'
const ErrorBoundaryFallback = () => {
const { error, reset } = useErrorBoundaryFallbackProps()
return (
<div>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
)
}
export const Example = () => (
<ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<Feed />
</ErrorBoundary>
)Common Mistakes
HIGH Using react-error-boundary prop interface on Suspensive ErrorBoundary
Wrong:
import { ErrorBoundary } from '@suspensive/react'
const Example = () => (
<ErrorBoundary FallbackComponent={ErrorFallback} onResetKeysChange={handleChange}>
<Feed />
</ErrorBoundary>
)Correct:
import { ErrorBoundary } from '@suspensive/react'
const Example = () => (
<ErrorBoundary fallback={({ error, reset }) => <ErrorFallback error={error} onRetry={reset} />}>
<Feed />
</ErrorBoundary>
)Suspensive has a single unified fallback prop (no FallbackComponent/fallbackRender/onResetKeysChange), and reset is exposed as reset in fallback props, not resetErrorBoundary.
Source: docs/suspensive.org/src/content/en/docs/react/comparison.mdx
HIGH Expecting fallback errors to re-render the same fallback
Wrong:
import { ErrorBoundary } from '@suspensive/react'
// assuming this boundary catches its own fallback's errors forever
const Example = ({ children }) => <ErrorBoundary fallback={<BrokenFallback />}>{children}</ErrorBoundary>Correct:
import { ErrorBoundary } from '@suspensive/react'
// wrap with a parent boundary to catch fallback errors
const Example = ({ children }) => (
<ErrorBoundary fallback={<LastResort />}>
<ErrorBoundary fallback={<BrokenFallback />}>{children}</ErrorBoundary>
</ErrorBoundary>
)Since v3, an error thrown inside a fallback propagates to the parent ErrorBoundary instead of recursively re-rendering the same fallback (the react-error-boundary and Suspensive v2 behavior) — agents trained on v2/react-error-boundary assume the old recursive behavior.
Source: docs/suspensive.org/src/content/en/docs/react/migration/migrate-to-v3.mdx (#1409)
HIGH Calling useErrorBoundaryFallbackProps outside a fallback
Wrong:
import { ErrorBoundary, useErrorBoundaryFallbackProps } from '@suspensive/react'
const Child = () => {
const { reset } = useErrorBoundaryFallbackProps() // throws
return <button onClick={reset}>retry</button>
}
const Example = () => (
<ErrorBoundary fallback={<Fallback />}>
<Child />
</ErrorBoundary>
)Correct:
import { ErrorBoundary, useErrorBoundaryFallbackProps } from '@suspensive/react'
const Fallback = () => {
const { error, reset } = useErrorBoundaryFallbackProps()
return <button onClick={reset}>{error.message}</button>
}
const Example = ({ children }) => <ErrorBoundary fallback={<Fallback />}>{children}</ErrorBoundary>The hook throws a runtime error when called in children or outside an ErrorBoundary; it is only valid inside the fallback element.
Source: docs/suspensive.org/src/content/en/docs/react/ErrorBoundary.mdx (warning callout)
HIGH Passing callback fallback from a Server Component
Wrong:
// app/page.tsx — Server Component
import { ErrorBoundary } from '@suspensive/react'
export default function Page() {
return (
<ErrorBoundary fallback={({ error }) => <p>{error.message}</p>}>
<Content />
</ErrorBoundary>
)
}Correct:
// app/page.tsx — Server Component
import { ErrorBoundary } from '@suspensive/react'
import { ErrorFallbackClient } from './ErrorFallbackClient'
export default function Page() {
return (
<ErrorBoundary fallback={<ErrorFallbackClient />}>
<Content />
</ErrorBoundary>
)
}// ErrorFallbackClient.tsx
'use client'
import { useErrorBoundaryFallbackProps } from '@suspensive/react'
export const ErrorFallbackClient = () => {
const { error, reset } = useErrorBoundaryFallbackProps()
return <button onClick={reset}>{error.message}</button>
}In RSC, functions cannot cross the server-to-client boundary; pass JSX as fallback and read error/reset via useErrorBoundaryFallbackProps in a client component.
Source: docs/suspensive.org/src/content/en/docs/react/ErrorBoundary.mdx (useErrorBoundaryFallbackProps section)
HIGH Expecting ErrorBoundary to reset itself on remount
Wrong:
import { ErrorBoundary } from '@suspensive/react'
// navigating away and back, expecting the boundary to be clean
const Example = ({ children }) => <ErrorBoundary fallback={<ErrorUI />}>{children}</ErrorBoundary>Correct:
import { ErrorBoundary } from '@suspensive/react'
import { useLocation } from 'react-router-dom'
const Example = ({ children }) => {
const location = useLocation()
return (
<ErrorBoundary resetKeys={[location.key]} fallback={({ reset }) => <button onClick={reset}>retry</button>}>
{children}
</ErrorBoundary>
)
}A caught error persists across remounts by design — reset explicitly via the fallback's reset, resetKeys, or ErrorBoundaryGroup; when the error came from a query, the query error cache must be reset too (onReset={useQueryErrorResetBoundary().reset}).
Source: https://github.com/toss/suspensive/issues/91
MEDIUM Manually filtering errors by rethrowing inside fallback
Wrong:
import { ErrorBoundary } from '@suspensive/react'
import { NetworkError } from './errors'
const Example = ({ children }) => (
<ErrorBoundary
fallback={({ error }) => {
if (!(error instanceof NetworkError)) throw error
return <NetworkErrorUI />
}}
>
{children}
</ErrorBoundary>
)Correct:
import { ErrorBoundary } from '@suspensive/react'
import { NetworkError } from './errors'
const Example = ({ children }) => (
<ErrorBoundary shouldCatch={NetworkError} fallback={<NetworkErrorUI />}>
{children}
</ErrorBoundary>
)shouldCatch filters declaratively before the boundary catches, keeps TypeScript error narrowing, and avoids relying on fallback-rethrow semantics (which changed in v3).
Source: docs/suspensive.org/src/content/en/docs/react/ErrorBoundary.mdx (shouldCatch section)
MEDIUM Assuming caught values are always Error instances
Wrong:
import { ErrorBoundary } from '@suspensive/react'
const Example = ({ children }) => (
<ErrorBoundary fallback={({ error }) => <p>{error.message}</p>}>{children}</ErrorBoundary>
)Correct:
import { ErrorBoundary } from '@suspensive/react'
const Example = ({ children }) => (
<ErrorBoundary fallback={({ error }) => <p>{error instanceof Error ? error.message : String(error)}</p>}>
{children}
</ErrorBoundary>
)Thrown strings and plain objects reach the fallback as-is, so fallback code assuming error.message renders undefined (or crashes) on non-Error throws.
Source: https://github.com/toss/suspensive/issues/1785
See also: migrate-to-v3 — v3 changed fallback error propagation semantics. See also: the suspensive-react composition skill in @suspensive/react-query-4/@suspensive/react-query-5 — the removed QueryErrorBoundary replacement is ErrorBoundary + useQueryErrorResetBoundary, and shouldCatch layering applies to API error types.