리액트로 개발을 하다 에러가 생기면 해당 부분의 컴포넌트만 렌더링되지 않는것이 아니라 전부 다 렌더링이 되지 않는다. 다시말해 리액트에서는 예외 발생 시 예외를 발생한 컴포넌트만 언마운트 되지 않는다. 예외 발생 시 모든 컴포넌트가 언마운트 된다.
이는 잘못된 정보를 사용자에게 보여 주는 것보다는 아무것도 보여주지 않는 것이 낫기 때문이다.
하지만, 사용자가 보기에 아무것도 보여주지 않는 것은 잘못된 정보를 제공하지 않았을 뿐이지 나쁜 사용자 경험을 제공하게 된다. 차라리 어디가 에러가 나고 있는지 보여주는게 백번 낫다.
이러한 문제점을 해결하기 위해 등장한 것이 Error Boundary
이다
에러 경계는 하위 컴포넌트 트리의 어디에서든 자바스크립트 에러를 기록하며 깨진 컴포넌트 트리 대신 폴백 UI를 보여주는 React 컴포넌트입니다. 에러 경계는 렌더링 도중 생명주기 메서드 및 그 아래에 있는 전체 트리에서 에러를 잡아냅니다.
공식문서에 나온 예외사항들은 아래 4가지다.
Note
에러 경계는 다음과 같은 에러는 포착하지 않습니다.
- 이벤트 핸들러
- 비동기적 코드 (예: setTimeout 혹은 requestAnimationFrame 콜백)
- 서버 사이드 렌더링
- 자식에서가 아닌 에러 경계 자체에서 발생하는 에러
공식 문서 예제 코드
난 여기서 3가지만 커스텀했다
- export 만 추가.
- import React from "react" 추가
- fallbackImage 와 text 추가.
✍ ErrorBoundary.jsx
import React from "react";
import fallbackImage from "../assets/fallbackImage.png";
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 다음 렌더가 폴백 UI를 표시하도록 상태를 업데이트합니다.
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
/// 오류 보고 서비스에 오류를 기록할 수도 있습니다
console.log(error, errorInfo);
}
render() {
if (this.state.hasError) {
/// 사용자 지정 폴백 UI를 렌더링할 수 있습니다
// return <h1>Something went wrong.</h1>;
return (
<div
style={{
display: "flex-box",
textAlign: "center",
// justifyContent: "center",
}}
>
<h1 style={{ display: "block" }}>무언가 오류가 있어요.</h1>
<img src={fallbackImage} alt="fallback" />;
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
✍ main.jsx
import React from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import ErrorBoundary from "./components/ErrorBoundary";
import Posts, { loader as postsLoader } from "./routes/Posts";
import NewPost from "./routes/NewPost";
import RootLayout from "./routes/RootLayout";
import "./index.css";
const router = createBrowserRouter([
{
path: "/",
element: <RootLayout />,
children: [
{
path: "/",
element: <Posts />,
loader: postsLoader,
children: [{ path: "/create-post", element: <NewPost /> }],
},
],
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<ErrorBoundary>
<RouterProvider router={router} />
</ErrorBoundary>
</React.StrictMode>
);
코드를 설명하자면, 에러의 상태를 state로 관리하고, 에러가 발생하면 이를 getDerivedStateFromError로 확인하여 에러의 상태를 바꿔 fallback UI를 보여주는 식이다.
그리고 사용하고 싶은 부분에 import 한 다음에 감싸주면 된다. 난 그냥 스크립트를 따로 뺴서 가장 최상단의 라우터 루트 컴포넌트를 감싸주었다.
코드를 제대로 짰는데 자꾸만 fallback 이미지가 안나왔다. 이것도 찾는데 참 오래걸렸다. 다른 블로그에서도 다들 똑같은 말만 할 뿐 방법을 알려주지 않았다.
결론부터 이야기하면 문제는 main.jsx
에서 <RouterProvider router={router} />
부분을 ErrorBoundary
로 감싼것이었다.
래핑할 부분을 RouterProvider
가 아니라 RootLayout
자식 라우트인 Posts
컴포넌트를 감싸면 해결된다. 즉 이렇게 하면 Posts
의 아래로 렌더링 되는 곳에서부터 문제가 발생하면 내가 import 한 fallback UI가 나오게 된다.
RouterProvider
래핑하면 안되는 이유일반적으로 ErrorBoundary 구성 요소는 전체 응용 프로그램이 아닌 응용 프로그램의 특정 부분에 대한 오류를 처리하도록 설계되었기 때문에 전체 응용 프로그램을 ErrorBoundary로 래핑하는 것은 권장되지 않습니다.
전체 응용 프로그램을 ErrorBoundary로 래핑하면 ErrorBoundary 구성 요소 외부에서 발생하는 모든 오류가 제대로 포착 및 처리되지 않습니다. 이로 인해 예기치 않은 동작이 발생할 수 있으며 애플리케이션에서 문제를 디버깅하기 어려울 수 있습니다.
대신 구성 요소 계층의 적절한 수준에서 ErrorBoundary 구성 요소를 사용하여 응용 프로그램의 특정 부분 내에서 발생하는 오류를 처리하는 것이 좋습니다. 이를 통해 오류를 보다 효과적으로 격리하고 처리하는 동시에 사용자를 위한 대체 UI를 계속 제공할 수 있습니다.
✍ main.jsx (래핑 컴포넌트 수정 후)
import React from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import ErrorBoundary from "./components/ErrorBoundary";
import Posts, { loader as postsLoader } from "./routes/Posts";
import NewPost from "./routes/NewPost";
import RootLayout from "./routes/RootLayout";
import "./index.css";
const router = createBrowserRouter([
{
path: "/",
element: <RootLayout />,
children: [
{
path: "/",
element: (
<ErrorBoundary>
<Posts />
</ErrorBoundary>
),
loader: postsLoader,
children: [{ path: "/create-post", element: <NewPost /> }],
},
],
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
Error Boundary와 Fallback UI 를 설명해주는 리액트 공식문서
https://ko.reactjs.org/docs/error-boundaries.html