👀 Check out the changes in Suspensive v2. read more →
Documentation
@suspensive/react
<ErrorBoundary/>

ErrorBoundary

this component can handle any errors in children.

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>
)
💡

Use <ErrorBoundary/> fallback props without prop drilling

useErrorBoundaryFallbackProps

If component using reset method and error object is nested, prop drilling cannot be avoided. The useErrorBoundaryFallbackProps allows you to access the reset method and error objects without prop drilling.

import { ErrorBoundary, useErrorBoundaryFallbackProps } from '@suspensive/react'
 
const Nested = () => {
  const { reset, error } = useErrorBoundaryFallbackProps()
 
  return (
    <>
      <button onClick={reset}>Try again</button>
      {error.message}
    </>
  )
}
 
// There's no need to pass fallback Prop here!
const ErrorBoundaryFallback = () => <Nested />
 
const Example = () => (
  <ErrorBoundary fallback={ErrorBoundaryFallback}>
    <Error />
  </ErrorBoundary>
)

props.resetKeys

If you want to reset <ErrorBoundary/> by component where is outside of <ErrorBoundary/>'s fallback. Inject any resetKey in resetKeys. resetKeys work only when at least one element of array is changed. you don't need to worry about provide 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

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={false}
        fallback={({ error }) => <>Child ErrorBoundary fallback: {error.message}</>}
      >
        <CustomErrorAfter2s />
      </ErrorBoundary>
    </ErrorBoundary>
  )
}

You can also apply multiple criteria through 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</>
}

useErrorBoundary

useErrorBoundary().setError

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

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</>
}
💡

Controlling multiple <ErrorBoundary/>s

<ErrorBoundary/> is more powerful when used with <ErrorBoundaryGroup/>. Control multiple <ErrorBoundary/>s with <ErrorBoundaryGroup/>.
Details are introduced in <ErrorBoundaryGroup/> page.