| name | error-handling |
| description | 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.
|
| metadata | {"type":"sub-skill","library":"@suspensive/react","library_version":"3.21.2","framework":"react"} |
| requires | ["react"] |
| sources | ["toss/suspensive:docs/suspensive.org/src/content/en/docs/react/ErrorBoundary.mdx","toss/suspensive:docs/suspensive.org/src/content/en/docs/react/ErrorBoundaryGroup.mdx","toss/suspensive:packages/react/src/ErrorBoundary.tsx"] |
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'
const Example = ({ children }) => <ErrorBoundary fallback={<BrokenFallback />}>{children}</ErrorBoundary>
Correct:
import { ErrorBoundary } from '@suspensive/react'
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()
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:
import { ErrorBoundary } from '@suspensive/react'
export default function Page() {
return (
<ErrorBoundary fallback={({ error }) => <p>{error.message}</p>}>
<Content />
</ErrorBoundary>
)
}
Correct:
import { ErrorBoundary } from '@suspensive/react'
import { ErrorFallbackClient } from './ErrorFallbackClient'
export default function Page() {
return (
<ErrorBoundary fallback={<ErrorFallbackClient />}>
<Content />
</ErrorBoundary>
)
}
'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'
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.