Skip to Content
문서@suspensive/react<ErrorBoundary/>

ErrorBoundary

이 컴포넌트는 children에 에러가 발생하면 제어할 수 있습니다.

비교

@suspensive/react<ErrorBoundary/>는 React의 클래스 기반 에러 경계와 인기 있는 react-error-boundary@sentry/react와 같은 에러 경계 라이브러리에 대한 선언적이고 기능이 풍부한 대안을 제공합니다.

@suspensive/reactreact-error-boundary@sentry/reactDIY (Class Component)
Error BoundaryshouldCatch
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 RenderingSSR-safe Suspense (clientOnly)
Flash-of-loading prevention (Delay)
Global default fallbacks (DefaultPropsProvider)
Declarative data fetching (SuspenseQuery)
Client-only rendering (ClientOnly)
import { ErrorBoundary } from '@suspensive/react' const SuspensiveExample = () => ( <ErrorBoundary fallback={({ error, reset }) => ( <div> <button onClick={reset}>리셋</button> {error.message} </div> )} > <YourComponent /> </ErrorBoundary> )

@suspensive/react의 주요 장점

  1. shouldCatch를 통한 고급 에러 필터링: 다른 솔루션과 달리, @suspensive/react는 boolean, ErrorConstructor 또는 콜백 매처를 사용하여 특정 에러를 조건부로 캐치할 수 있습니다. 이를 통해 부모와 자식 ErrorBoundary가 서로 다른 에러 타입을 처리할 수 있는 정교한 에러 처리 전략을 구현할 수 있습니다.

  2. 적절한 Fallback 에러 처리: react-error-boundary와 달리, fallback 컴포넌트에서 발생한 에러는 같은 경계에서 재귀적으로 캐치되는 대신 부모 ErrorBoundary로 전달됩니다. 이를 통해 무한 fallback 루프를 방지하고 더 예측 가능한 에러 처리 동작을 제공합니다. 자세히 알아보기

  3. useErrorBoundaryFallbackProps: fallback 컴포넌트에서 prop drilling을 제거하고 훅을 통해 errorreset에 직접 접근할 수 있어, 깊게 중첩된 fallback UI를 훨씬 깔끔하게 만들 수 있습니다.

  4. ErrorBoundaryGroup: 여러 ErrorBoundary를 함께 관리하고 리셋할 수 있으며, 조정된 리셋 동작이 필요한 여러 에러 경계가 있는 복잡한 UI에 완벽합니다.

  5. 더 나은 TypeScript 지원: shouldCatch 구성을 기반으로 에러 타입에 대한 고급 타입 추론을 제공하여 더 나은 자동 완성과 타입 안정성을 제공합니다.

  6. 클래스 컴포넌트 불필요: 네이티브 React 에러 경계와 달리, 클래스 컴포넌트를 작성하지 않고도 완전히 선언적인 함수 컴포넌트 기반 접근 방식을 사용할 수 있습니다.

마이그레이션 가이드

react-error-boundary를 사용하고 있다면, @suspensive/react로 마이그레이션하는 것은 간단합니다:

// react-error-boundary import { ErrorBoundary } from 'react-error-boundary' const ReactErrorBoundaryExample = () => ( <ErrorBoundary fallbackRender={({ error, resetErrorBoundary }) => ( <div> <button onClick={resetErrorBoundary}>리셋</button> {error.message} </div> )} onReset={() => console.log('reset')} > <YourComponent /> </ErrorBoundary> ) // @suspensive/react - 동일한 기능 import { ErrorBoundary } from '@suspensive/react' const SuspensiveExample = () => ( <ErrorBoundary fallback={({ error, reset }) => ( <div> <button onClick={reset}>리셋</button> {error.message} </div> )} onReset={() => console.log('reset')} > <YourComponent /> </ErrorBoundary> )

주요 API 차이점:

  • fallback, fallbackRender, FallbackComponentfallback
  • resetErrorBoundaryreset (fallback props에서)

props.fallback

<ErrorBoundary/>의 children에 error가 발생하면 error는 잡히고 fallback이 렌더링됩니다.

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

<ErrorBoundary/>의 fallback으로 전달할 컴포넌트 정의하기

ErrorBoundaryFallbackProps

<ErrorBoundary/>의 fallback으로 컴포넌트를 전달하고 싶다면 ErrorBoundaryFallbackProps 타입을 활용해 쉽게 컴포넌트를 선언할 수 있습니다.

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

<ErrorBoundary/>의 fallback 외부에 있는 컴포넌트가 <ErrorBoundary/>를 reset하려면 resetKeys배열에 resetKey를 할당하면 됩니다. resetKeys는 배열의 하나 이상의 요소가 변경된 경우에만 작동합니다. useEffect의 종속성 배열이 작동하는 방식과 같이 resetKeys로 매 렌더링마다 새 배열을 주입하는 것을 걱정할 필요도 없습니다.

import { ErrorBoundary } from '@suspensive/react' import { useState, useEffect } from 'react' 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> </> ) }
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

<ErrorBoundary/>가 reset할 때 먼저 호출되는 callback입니다. @tanstack/react-query와는 아래와 같이 사용할 수 있습니다.

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

<ErrorBoundary/>가 error를 잡을 때 호출되는 callback입니다.

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 — Suspensive 고유 기능

react-error-boundary나 @sentry/react에는 없는 기능입니다. shouldCatch@suspensive/react만의 고유 기능으로, 계층화된 에러 처리를 가능하게 합니다 — 부모와 자식 ErrorBoundary가 서로 다른 에러 타입을 처리할 수 있습니다. 전체 비교를 확인하세요.

shouldCatch는 조건에 따라 <ErrorBoundary/>가 에러를 잡을지 결정합니다.

Boolean, ErrorConstructor, Callback의 3가지 기준을 받으며 기본값은 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> ) }

배열을 통해 여러 조건을 적용할 수도 있습니다.

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<ErrorBoundary/>의 props를 설정할 수 있는 HOC입니다. ErrorBoundary.with를 사용하면 컴포넌트를 쉽게 래핑할 수 있습니다.

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

useErrorBoundary

useErrorBoundary().setError

<ErrorBoundary/>의 children에서 useErrorBoundary().setError을 사용해 throw 없이도 <ErrorBoundary/>에서 Error를 알도록 할 수 있습니다.

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

<ErrorBoundary/>의 fallback 내에서 error 객체와 reset 메소드에 prop drilling 없이 접근할 수 있게 해주는 훅입니다.

Next.js의 React Server Component 환경에서는 서버 컴포넌트에서 클라이언트 컴포넌트로 callback 함수를 props로 전달할 수 없습니다. 이로 인해 <ErrorBoundary/>fallback에 함수형 컴포넌트를 전달하여 errorreset을 받는 것이 불가능합니다. useErrorBoundaryFallbackProps를 사용하면 이러한 제약 없이 fallback 내부에서 errorreset에 접근할 수 있습니다.

또한 fallback 컴포넌트가 깊게 중첩되는 경우에도, errorreset을 여러 단계에 걸쳐 전달해야 하는 prop drilling 문제를 해결합니다.

import { ErrorBoundary, useErrorBoundaryFallbackProps } from '@suspensive/react' const ErrorBoundaryFallback = () => { const { reset, error } = useErrorBoundaryFallbackProps() return ( <> <button onClick={reset}>Try again</button> {error.message} </> ) } // RSC에서는 fallback에 callback을 전달하지 않고 JSX를 직접 사용해야 합니다 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('에러가 발생했습니다') })
    }, 2000)
  }, [])

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

  return <>No error</>
}

useErrorBoundaryFallbackProps는 반드시 <ErrorBoundary/>fallback 내부에서 호출해야 합니다. <ErrorBoundary/>children 또는 <ErrorBoundary/> 외부에서 호출하면 에러가 발생합니다.

수정된 날짜: