๐ŸŽ‰ Suspensive v2๊ฐ€ ์ถœ์‹œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ๋”๋ณด๊ธฐ โ†’
๋ฌธ์„œ๋ณด๊ธฐ
@suspensive/react
<ErrorBoundary/>

ErrorBoundary

์ด ์ปดํฌ๋„ŒํŠธ๋Š” children์— ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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}
      </>
    )}
  >
    <ErrorAfter4s />
  </ErrorBoundary>
)
 
const ErrorAfter4s = () => {
  const [asyncState, setAsyncState] = useState<{ isError: true; error: Error } | { isError: false; error: null }>({
    isError: false,
    error: null,
  })
 
  useEffect(() => {
    setTimeout(() => {
      setAsyncState({ isError: true, error: { status: 401, message: 'unauthorized' } })
    }, 4000)
  }, [])
 
  if (asyncState.isError) {
    throw asyncState.error
  }
 
  return <>No error</>
}

errorboundary example

<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}>
    <ErrorAfter4s />
  </ErrorBoundary>
)
๐Ÿ’ก

<ErrorBoundary/> fallback props์„ prop drilling ์—†์ด ์‚ฌ์šฉํ•˜๊ธฐ

useErrorBoundaryFallbackProps

error ๊ฐ์ฒด์™€ reset ๋ฉ”์†Œ๋“œ์„ ์‚ฌ์šฉํ•˜๋ ค๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ค‘์ฒฉ๋˜๋ฉด prop drilling์„ ํ”ผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด ๋•Œ, useErrorBoundaryFallbackProps์„ ํ†ตํ•ด, prop drilling ์—†์ด reset ๋ฉ”์†Œ๋“œ์™€ error ๊ฐ์ฒด์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { ErrorBoundary, useErrorBoundaryFallbackProps } from '@suspensive/react'
 
const Nested = () => {
  const { reset, error } = useErrorBoundaryFallbackProps()
 
  return (
    <>
      <button onClick={reset}>Try again</button>
      {error.message}
    </>
  )
}
 
// ์—ฌ๊ธฐ์„œ fallbackProp ์„ ์ „๋‹ฌํ•  ํ•„์š”๊ฐ€ ์—†์–ด์ง‘๋‹ˆ๋‹ค!
const ErrorBoundaryFallback = () => <Nested />
 
const Example = () => (
  <ErrorBoundary fallback={ErrorBoundaryFallback}>
    <Error />
  </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]}>
        <ErrorAfter4s />
      </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>
)

props.onError

<ErrorBoundary/>๊ฐ€ error๋ฅผ ์žก์„ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” callback์ž…๋‹ˆ๋‹ค.

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

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

withErrorBoundary

๐Ÿšซ

deprecated: wrap.ErrorBoundary().on์„ ๋Œ€์‹  ์‚ฌ์šฉํ•˜์„ธ์š”

withErrorBoundary๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ๋ฅผ <ErrorBoundary/>๋กœ ์‰ฝ๊ฒŒ ๋ž˜ํ•‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์ด withErrorBoundary๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ด๋ฅผ ๊ฐ์‹ธ๊ธฐ ์œ„ํ•ด ๋ถˆํ•„์š”ํ•œ ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. withErrorBoundary์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ž๋Š” children์ด ์—†๋Š” <ErrorBoundary/>์˜ props์ž…๋‹ˆ๋‹ค.

import { withErrorBoundary, useErrorBoundary } from '@suspensive/react'
 
const Example = withErrorBoundary(
  function Component() {
    const errorBoundary = useErrorBoundary()
 
    return <>...</>
  },
  { fallback: ErrorBoundaryFallback }
)
๐Ÿ’ก

๋‹ค์ˆ˜์˜ <ErrorBoundary/>๋ฅผ ์ œ์–ดํ•˜๊ธฐ

<ErrorBoundary/>์€ <ErrorBoundaryGroup/>๊ณผ ์‚ฌ์šฉํ•˜๋ฉด ๋” ๊ฐ•๋ ฅํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. <ErrorBoundaryGroup/>๋กœ ๋‹ค์ˆ˜์˜ <ErrorBoundary/>๋ฅผ ์ œ์–ดํ•˜์„ธ์š”.
์ž์„ธํ•œ ๋‚ด์šฉ์€ <ErrorBoundaryGroup/>ํŽ˜์ด์ง€์—์„œ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค.