Skip to Content
Docs@suspensive/react<ErrorBoundary/>

ErrorBoundary

This component can handle any errors in children.

Comparison

@suspensive/react’s <ErrorBoundary/> provides a declarative, feature-rich alternative to React’s class-based error boundaries and popular error boundary libraries like react-error-boundary and @sentry/react.

Feature@suspensive/reactreact-error-boundary@sentry/reactReact Class Component
Basic error catching
Fallback UI with error & reset⚠️ (Manual)
Reset with resetKeys⚠️ (Manual)
onReset callback⚠️ (Manual)
onError callback✅ (componentDidCatch)
Conditional error catching (shouldCatch)⚠️ (Manual)
Fallback error handling✅ (To parent)❌ (Recursive)❌ (Recursive)⚠️ (Manual)
useErrorBoundary hook
useErrorBoundaryFallbackProps hook
ErrorBoundaryGroup
HOC support✅ (ErrorBoundary.with)✅ (withErrorBoundary)✅ (withErrorBoundary)
TypeScript error type inference✅ (Advanced)✅ (Basic)✅ (Basic)⚠️ (Manual)
Declarative API
Automatic error reporting✅ (ErrorBoundary.Observer + Sentry)✅ (To Sentry)
import { ErrorBoundary } from '@suspensive/react' const SuspensiveExample = () => ( <ErrorBoundary fallback={({ error, reset }) => ( <div> <button onClick={reset}>Reset</button> {error.message} </div> )} > <YourComponent /> </ErrorBoundary> )

Key Advantages of @suspensive/react

  1. Advanced Error Filtering with shouldCatch: Unlike other solutions, @suspensive/react allows you to conditionally catch specific errors using boolean, ErrorConstructor, or callback matchers. This enables sophisticated error handling strategies where parent and child ErrorBoundaries can handle different error types.

  2. Proper Fallback Error Handling: Unlike react-error-boundary, errors thrown in fallback components are passed to the parent ErrorBoundary instead of being caught recursively by the same boundary. This prevents infinite fallback loops and provides more predictable error handling behavior. Learn more

  3. useErrorBoundaryFallbackProps: Eliminates prop drilling in fallback components by providing direct access to error and reset through a hook, making deeply nested fallback UIs much cleaner.

  4. ErrorBoundaryGroup: Manage and reset multiple ErrorBoundaries together, perfect for complex UIs with multiple error boundaries that need coordinated reset behavior.

  5. Better TypeScript Support: Advanced type inference for error types based on shouldCatch configuration, providing better autocomplete and type safety.

  6. No Class Components Required: Unlike native React error boundaries, you can use a fully declarative, function component-based approach without writing class components.

Migration Guide

If you’re using react-error-boundary, migrating to @suspensive/react is straightforward:

// react-error-boundary import { ErrorBoundary } from 'react-error-boundary' const ReactErrorBoundaryExample = () => ( <ErrorBoundary fallbackRender={({ error, resetErrorBoundary }) => ( <div> <button onClick={resetErrorBoundary}>Reset</button> {error.message} </div> )} onReset={() => console.log('reset')} > <YourComponent /> </ErrorBoundary> ) // @suspensive/react - same functionality import { ErrorBoundary } from '@suspensive/react' const SuspensiveExample = () => ( <ErrorBoundary fallback={({ error, reset }) => ( <div> <button onClick={reset}>Reset</button> {error.message} </div> )} onReset={() => console.log('reset')} > <YourComponent /> </ErrorBoundary> )

Main API differences:

  • fallback, fallbackRender, FallbackComponentfallback
  • resetErrorBoundaryreset (in fallback props)

props.fallback

If there is any thrown error in children of <ErrorBoundary/>, Error will be caught and then fallback will be rendered.

import { ErrorBoundary } from '@suspensive/react' import { useState, useEffect } from 'react' const Example = () => ( <ErrorBoundary fallback={(props) => ( <> <button onClick={props.reset}>Try again</button> {props.error.message} </> )} > <ErrorAfter2s /> </ErrorBoundary> )
import { ErrorBoundary } from '@suspensive/react'
import { useState, useEffect } from 'react'
import { ErrorAfter2s } from './ErrorAfter2s'

export const Example = () => {
  return (
    <ErrorBoundary
      fallback={(props) => (
        <>
          <button onClick={props.reset}>Try again</button>
          {props.error.message}
        </>
      )}
    >
      <ErrorAfter2s />
    </ErrorBoundary>
  )
}

Define component as <ErrorBoundary/>’s fallback

ErrorBoundaryFallbackProps

If you want to deliver a declared component as <ErrorBoundary/>’s fallback, you can use the ErrorBoundaryFallbackProps type to declare the component easily.

import type { ErrorBoundaryFallbackProps } from '@suspensive/react' const ErrorBoundaryFallback = ({ reset, error, }: ErrorBoundaryFallbackProps) => ( <> <button onClick={reset}>reset</button> {error.message} </> ) const Example = () => ( <ErrorBoundary fallback={ErrorBoundaryFallback}> <ErrorAfter2s /> </ErrorBoundary> )

props.resetKeys

If you want to reset <ErrorBoundary/> by a component that is outside of <ErrorBoundary/>’s fallback. Pass any resetKey into resetKeys. resetKeys work only when at least one element of array is changed. you don’t need to worry about providing a new array as resetKeys like how useEffect’s dependency array work.

import { ErrorBoundary } from '@suspensive/react' import { useState } from 'react' const Example = () => { const [resetKey, setResetKey] = useState(0) return ( <> <button onClick={() => setResetKey((prev) => prev + 1)}>Try again</button> <ErrorBoundary resetKeys={[resetKey]}> <ErrorAfter2s /> </ErrorBoundary> </> ) }
import { ErrorBoundary } from '@suspensive/react'
import { useState, useEffect } from 'react'
import { ErrorAfter2s } from './ErrorAfter2s'

export const Example = () => {
  const [resetKey, setResetKey] = useState(0)

  return (
    <>
      <button onClick={() => setResetKey((prev) => prev + 1)}>Try again</button>
      <ErrorBoundary
        resetKeys={[resetKey]}
        fallback={(props) => <>{props.error.message}</>}
      >
        <ErrorAfter2s />
      </ErrorBoundary>
    </>
  )
}

props.onReset

This is a callback that is called first when <ErrorBoundary/> reset. It can be used with @tanstack/react-query as follows.

import { ErrorBoundary } from '@suspensive/react' import { QueryErrorResetBoundary } from '@tanstack/react-query' const Example = () => ( <QueryErrorResetBoundary> {({ reset }) => ( <ErrorBoundary onReset={reset} fallback={(props) => ( <> <button onClick={props.reset}>Try again</button> {props.error.message} </> )} > <Page /> </ErrorBoundary> )} </QueryErrorResetBoundary> )
import { ErrorBoundary } from '@suspensive/react'
import { QueryErrorResetBoundary } from '@tanstack/react-query'
import { Page } from './Page'

export const Example = () => (
  <QueryErrorResetBoundary>
    {({ reset }) => (
      <ErrorBoundary
        onReset={reset}
        fallback={(props) => (
          <>
            <button onClick={props.reset}>Try again</button>
            {props.error.message}
          </>
        )}
      >
        <Page />
      </ErrorBoundary>
    )}
  </QueryErrorResetBoundary>
)

props.onError

This is a callback called when <ErrorBoundary/> catches an error.

import { ErrorBoundary } from '@suspensive/react' const logError = (error: Error, info: ErrorInfo) => { // ... } const Example = ( <ErrorBoundary fallback={ErrorBoundaryFallback} onError={logError}> <ErrorAfter2s /> </ErrorBoundary> )
import { ErrorBoundary } from '@suspensive/react'
import { ErrorAfter2s } from './ErrorAfter2s'

const logError = (error: Error, info: ErrorInfo) => {
  console.log(error, info)
}

export const Example = () => {
  return (
    <ErrorBoundary
      fallback={(props) => (
        <>
          <button onClick={props.reset}>Try again</button>
          {props.error.message}
        </>
      )}
      onError={logError}
    >
      <ErrorAfter2s />
    </ErrorBoundary>
  )
}

props.shouldCatch — Unique to Suspensive

Not available in react-error-boundary or @sentry/react. shouldCatch is a feature unique to @suspensive/react that enables layered error handling — parent and child ErrorBoundaries can handle different error types. See the full comparison.

shouldCatch determines whether <ErrorBoundary/> should catch errors based on conditions.

It accepts three criteria: Boolean, ErrorConstructor, and Callback, and defaults to true.

import { ErrorBoundary } from '@suspensive/react' import { useState, useEffect, createElement } from 'react' export const Example = () => { return ( <ErrorBoundary fallback={({ error }) => ( <>Parent ErrorBoundary fallback: {error.message}</> )} > <ErrorBoundary shouldCatch={CustomError} fallback={({ error }) => ( <>Child ErrorBoundary fallback: {error.message}</> )} > <CustomErrorAfter2s /> </ErrorBoundary> </ErrorBoundary> ) }

You can also apply multiple criteria through an array.

import { ErrorBoundary } from '@suspensive/react' import { useState, useEffect, createElement } from 'react' const Example = () => { return ( <ErrorBoundary fallback={({ error }) => ( <>Parent ErrorBoundary fallback: {error.message}</> )} > <ErrorBoundary shouldCatch={[ false, CustomError, (error) => error instanceof CustomError, ]} fallback={({ error }) => ( <>Child ErrorBoundary fallback: {error.message}</> )} > <CustomErrorAfter2s /> </ErrorBoundary> </ErrorBoundary> ) }
import { ErrorBoundary } from '@suspensive/react'
import { useState, useEffect, createElement } from 'react'

export const Example = () => {
  return (
    <ErrorBoundary
      fallback={({ error }) => (
        <>Parent ErrorBoundary fallback: {error.message}</>
      )}
    >
      <ErrorBoundary
        shouldCatch={CustomError}
        fallback={({ error }) => (
          <>Child ErrorBoundary fallback: {error.message}</>
        )}
      >
        <CustomErrorAfter2s />
      </ErrorBoundary>
    </ErrorBoundary>
  )
}

export class CustomError extends Error {
  constructor(...args: ConstructorParameters<ErrorConstructor>) {
    super(...args)
    console.error(...args)
  }
}

export const CustomErrorAfter2s = () => {
  const [asyncState, setAsyncState] = useState<
    { isError: true; error: CustomError } | { isError: false; error: null }
  >({
    isError: false,
    error: null,
  })

  useEffect(() => {
    setTimeout(() => {
      setAsyncState({
        isError: true,
        error: () => new CustomError('error made by CustomError'),
      })
    }, 2000)
  }, [])

  if (asyncState.isError) {
    throw asyncState.error()
  }

  return <>No error</>
}

import { ErrorBoundary } from '@suspensive/react'
import { useState, useEffect, createElement } from 'react'

export const Example = () => {
  return (
    <ErrorBoundary
      fallback={({ error }) => (
        <>Parent ErrorBoundary fallback: {error.message}</>
      )}
    >
      <ErrorBoundary
        shouldCatch={CustomError}
        fallback={({ error }) => (
          <>Child ErrorBoundary fallback: {error.message}</>
        )}
      >
        <ErrorAfter2s />
      </ErrorBoundary>
    </ErrorBoundary>
  )
}

export class CustomError extends Error {
  constructor(...args: ConstructorParameters<ErrorConstructor>) {
    super(...args)
    console.error(...args)
  }
}

export const ErrorAfter2s = () => {
  const [asyncState, setAsyncState] = useState<
    { isError: true; error: Error } | { isError: false; error: null }
  >({
    isError: false,
    error: null,
  })

  useEffect(() => {
    setTimeout(() => {
      setAsyncState({ isError: true, error: new Error('error made by Error') })
    }, 2000)
  }, [])

  if (asyncState.isError) {
    throw asyncState.error
  }

  return <>No error</>
}

ErrorBoundary.with

ErrorBoundary.with is a higher-order component that wraps the component with <ErrorBoundary/>. ErrorBoundary.with makes it easy to wrap a component.

import { ErrorBoundary } from '@suspensive/react' const Example = ErrorBoundary.with({ fallback: ErrorBoundaryFallback }, () => { const errorBoundary = useErrorBoundary() return <>...</> })

ErrorBoundary.Observer

This is an experimental feature.

ErrorBoundary.Observer is a component that observes errors from all nested <ErrorBoundary/> components. It does not catch or handle errors — it simply observes them, making it ideal for integrating with error reporting tools like Sentry.

Previously, you had to attach onError prop individually to each <ErrorBoundary/>. To observe errors across the entire app, you had to repeatedly pass the same handler to every ErrorBoundary.

import { ErrorBoundary } from '@suspensive/react' import * as Sentry from '@sentry/react' // Before: repeated onError on every ErrorBoundary function App() { return ( <ErrorBoundary fallback={<FallbackA />} onError={Sentry.captureReactException} // repeated every time > <ErrorBoundary fallback={<FallbackB />} onError={Sentry.captureReactException} // repeated every time > <YourComponent /> </ErrorBoundary> </ErrorBoundary> ) }

With ErrorBoundary.Observer, you can observe errors from all nested <ErrorBoundary/> components at once. Both ErrorBoundary.Observer’s onError and each <ErrorBoundary/>’s own onError will be called when an error is caught.

import { ErrorBoundary } from '@suspensive/react' import * as Sentry from '@sentry/react' // After: single Observer wraps all ErrorBoundaries function App() { return ( <ErrorBoundary.Observer onError={Sentry.captureReactException}> <ErrorBoundary fallback={<FallbackA />}> <ErrorBoundary fallback={<FallbackB />}> <YourComponent /> </ErrorBoundary> </ErrorBoundary> </ErrorBoundary.Observer> ) }

Nested ErrorBoundary.Observer

Multiple ErrorBoundary.Observer components can be nested, and all ErrorBoundary.Observer’s onError will be called in bubble order (from inner to outer) when an error occurs.

import { ErrorBoundary } from '@suspensive/react' const Example = () => ( <ErrorBoundary.Observer onError={(error) => console.log('outer', error)}> <ErrorBoundary.Observer onError={(error) => console.log('inner', error)}> <ErrorBoundary fallback={({ error }) => <>{error.message}</>}> <YourComponent /> </ErrorBoundary> </ErrorBoundary.Observer> </ErrorBoundary.Observer> ) // When an error occurs, the order is: // 1. ErrorBoundary's onError (local) // 2. inner ErrorBoundary.Observer's onError // 3. outer ErrorBoundary.Observer's onError

useErrorBoundary

useErrorBoundary().setError

In children of <ErrorBoundary/>, we can use useErrorBoundary().setError to make <ErrorBoundary/> aware of the Error without throwing.

import { ErrorBoundary, useErrorBoundary } from '@suspensive/react' import { useEffect } from 'react' const Example = () => ( <ErrorBoundary fallback={ErrorBoundaryFallback}> <SetErrorAfterFetch /> </ErrorBoundary> ) const SetErrorAfterFetch = () => { const errorBoundary = useErrorBoundary() useEffect(() => { fetchSomething().then( (response) => {}, (error) => errorBoundary.setError(error) // instead of throw inside ) }, []) return <>No error</> }
import { ErrorBoundary, useErrorBoundary } from '@suspensive/react'
import { useEffect } from 'react'
import { ErrorBoundaryFallback } from './ErrorBoundaryFallback'
import { fetchSomething } from './fetchSomething'

export const Example = () => (
  <ErrorBoundary fallback={ErrorBoundaryFallback}>
    <SetErrorAfterFetch />
  </ErrorBoundary>
)

const SetErrorAfterFetch = () => {
  const errorBoundary = useErrorBoundary()

  useEffect(() => {
    fetchSomething().then(
      (response) => {},
      (error) => errorBoundary.setError(error) // instead of throw inside
    )
  }, [])

  return <>No error</>
}

useErrorBoundaryFallbackProps

A hook that allows you to access the error object and reset method without prop drilling inside <ErrorBoundary/>’s fallback.

In Next.js React Server Component environments, callback functions cannot be passed as props from server components to client components. This makes it impossible to pass a function component as <ErrorBoundary/>’s fallback to receive error and reset. useErrorBoundaryFallbackProps allows you to access error and reset inside the fallback without this limitation.

It also eliminates the prop drilling problem when fallback components are deeply nested, removing the need to pass error and reset through multiple layers.

import { ErrorBoundary, useErrorBoundaryFallbackProps } from '@suspensive/react' const ErrorBoundaryFallback = () => { const { reset, error } = useErrorBoundaryFallbackProps() return ( <> <button onClick={reset}>Try again</button> {error.message} </> ) } // In RSC, you need to pass JSX directly to fallback instead of a callback const Example = () => ( <ErrorBoundary fallback={<ErrorBoundaryFallback />}> <ErrorAfter2s /> </ErrorBoundary> )
import { ErrorBoundary, useErrorBoundaryFallbackProps } from '@suspensive/react'
import { useState, useEffect } from 'react'

const ErrorBoundaryFallback = () => {
  const { reset, error } = useErrorBoundaryFallbackProps()

  return (
    <div>
      <p>{error.message}</p>
      <button onClick={reset}>Try again</button>
    </div>
  )
}

export const Example = () => (
  <ErrorBoundary fallback={<ErrorBoundaryFallback />}>
    <ErrorAfter2s />
  </ErrorBoundary>
)

const ErrorAfter2s = () => {
  const [asyncState, setAsyncState] = useState<
    { isError: true; error: Error } | { isError: false; error: null }
  >({
    isError: false,
    error: null,
  })

  useEffect(() => {
    setTimeout(() => {
      setAsyncState({ isError: true, error: new Error('An error occurred') })
    }, 2000)
  }, [])

  if (asyncState.isError) {
    throw asyncState.error
  }

  return <>No error</>
}

useErrorBoundaryFallbackProps must be called inside <ErrorBoundary/>’s fallback. Calling it in <ErrorBoundary/>’s children or outside of <ErrorBoundary/> will throw an error.

Last updated on