
Error Boundary는 React 16부터 도입된 핵심 기능이다. 하위 컴포넌트 트리에서 발생한 자바스크립트 에러가 전체 애플리케이션을 중단시키지 않도록 보호하며, Fallback UI를 선언적으로 제공할 수 있어 사용자 경험을 크게 향상시킬 수 있다.
Error Boundary
Error Boundary는 하위 컴포넌트 트리 어디에서든 자바스크립트 에러가 발생했을 때, 해당 에러를 포착하여 기록하고 깨진 컴포넌트 대신 Fallback UI를 보여주는 React 컴포넌트이다. 클래스 컴포넌트에서 static getDerivedStateFromError()
와 componentDidCatch()
두 가지 메서드 중 하나 혹은 둘 다를 정의함으로써 Error Boundary로 동작하게 된다.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 에러 발생 시 다음 렌더링에서 Fallback UI를 보이도록 상태를 업데이트한다.
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 에러 정보 기록, 모니터링 서버 전송 등 사이드 이펙트 처리에 활용한다.
console.error('Error caught by ErrorBoundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
// Fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
위 코드에서 getDerivedStateFromError
를 사용하면 렌더 단계에서 에러 감지 후 hasError
상태를 true로 바꿔주어 다음 렌더링에 Fallback UI를 그릴 수 있다. 그리고 componentDidCatch
는 커밋 단계에서 호출되어 모니터링 서버에 에러를 기록하는 등 사이드 이펙트를 처리하기에 적합하다.
Error Boundary 배치
Error Boundary는 하위 트리의 특정 컴포넌트에서 발생한 에러만을 포착하기 때문에, 어느 범위까지 에러를 처리할지 결정해야 한다. 전체 애플리케이션을 감싸는 Global Error Boundary를 두어 심각한 에러를 처리하는 동시에, UI 일부(각 위젯이나 특정 섹션)를 감싸는 Error Boundary를 두어 나머지 영역이 정상적으로 동작하게끔 보호할 수도 있다.
<GlobalErrorBoundary>
<Header />
<ErrorBoundary>
<MainWidget />
</ErrorBoundary>
<Footer />
</GlobalErrorBoundary>
GlobalErrorBoundary
는 최상위 레벨에서 처리하지 못한 에러를 캐치해 전체 화면에 공용 Fallback UI를 렌더링할 수 있다. ErrorBoundary
는 내부 MainWidget
에서만 일어난 에러를 처리할 수 있다.
이벤트 핸들러, 비동기 에러
Error Boundary는 렌더링, 생명주기 메서드 내에서 발생한 에러만 포착한다. 이벤트 핸들러나 setTimeout
, requestAnimationFrame
등 비동기적으로 발생하는 에러는 잡아내지 않는다. 이벤트 핸들러 내부에서 에러를 잡아내려면 try/catch
구문을 사용해야 한다. 또한 비동기 요청에서 발생하는 에러(ex: Fetch, Axios 등)는 기본적으로 렌더 단계에 속해 있지 않다. React 16 에서는 별도의 처리 없이 단순히 내부에서 에러를 throw
하면 경계에 포착되지 않는다. 이를 해결하기 위해, 예를 들어 react-error-boundary
라이브러리나 React Query의 useQuery
에서 throwOnError
혹은 useErrorBoundary
설정 등을 활용하여 비동기 에러를 한 번 더 렌더링 흐름으로 올려줄 수 있다.
함수형 컴포넌트에서 사용하기
React 공식 문서의 예제 코드는 위에서 보았던 것처럼 클래스 컴포넌트 형태로 되어 있지만, 함수형 컴포넌트에서도 라이브러리를 통해 유사 기능을 구현할 수 있다. 대표적으로 react-error-boundary
가 있다.
import { ErrorBoundary } from 'react-error-boundary';
function FallbackComponent({ error, resetErrorBoundary }) {
return (
<div>
<p>Error : {error.message}</p>
<button onClick={() => resetErrorBoundary()}>
RESET
</button>
</div>
);
}
function App() {
return (
<ErrorBoundary
FallbackComponent={FallbackComponent}
onError={(error) => {
console.error('Error : ', error);
}}
onReset={() => {
console.log('Reset');
}}
>
<SomeComponent />
</ErrorBoundary>
);
}
위처럼 FallbackComponent
를 따로 정의하면 Fallback UI를 상황에 맞게 세부적으로 구성할 수 있고, resetErrorBoundary
함수를 통해 에러 상태를 재설정할 수도 있다.
React Query와의 연계
React Query는 비동기 요청을 선언적으로 관리하는 데 도움이 되는 라이브러리이며, useQuery
에서 throwOnError: true
같은 옵션을 주거나 useSuspenseQuery
를 사용할 수 있다. 이렇게 설정하면 데이터 패칭 과정에서 발생하는 에러를 자동으로 throw
하게 되고, 이를 Error Boundary가 잡아 Fallback UI를 렌더링할 수 있다.
const queryClient = new QueryClient({
defaultOptions: {
queries: {
useErrorBoundary: true,
suspense: true,
},
},
});
또는 react-error-boundary
와 @tanstack/react-query
의 QueryErrorResetBoundary
를 함께 사용하면 더욱 편리하다. QueryErrorResetBoundary
가 있으면, 에러가 발생한 후 다시 시도 버튼을 누를 때 바로 쿼리가 재설정되고, 재요청이 일어난다.
import { useQueryErrorResetBoundary } from '@tanstack/react-query';
import { ErrorBoundary } from 'react-error-boundary';
function Page() {
const { reset } = useQueryErrorResetBoundary();
return (
<ErrorBoundary
onReset={reset}
fallbackRender={({ resetErrorBoundary, error }) => (
<div>
에러 발생: {error.message}
<button onClick={() => resetErrorBoundary()}>
다시 시도
</button>
</div>
)}
>
<MyDataFetchingComponent />
</ErrorBoundary>
);
}
Reference
카카오페이 기술 블로그: React Query와 함께 Concurrent UI Pattern을 도입하는 방법
카카오 엔터테인먼트 FE 개발 블로그: React의 Error Boundary를 이용하여 효과적으로 에러 처리하기