1. Intro
2. Error Boundary
ErrorBoundary
라는 개념을 도입하였습니다.ErrorBoundary
를 통해 컴포넌트에서 에러가 발생
했을 때, 이를 캐치하고, 사용자들에게 에러가 발생하여 앱이 중단되는 것이 아닌 다른 대체 화면
을 보여줄 수 있습니다.ErrorBoundary
로 fallback UI를 보여줄 필요성이 있는 컴포넌트
를 감싸주고, 에러 발생 시 사용자에게 대체 화면을 보여준다면 에러를 적절히 핸들링하여 사용자의 경험이 향상될 수 있을 될 것이라고 생각합니다.상황에 맞는 처리
가 필요하다고 생각합니다.A 라는 콘텐츠에 100% 의존하는 페이지와 A 라는 콘텐츠에 10% 의존하는 페이지가 있다고 가정했을때, A 라는 콘텐츠에 100% 의존
하는 화면에 에러가 발생하면, 에러 페이지로 리다이렉트
되는것이 사용성에 더 좋다고 생각합니다.
반면, A 라는 콘텐츠에 일부만 의존
하는 화면에서 에러가 발생하면 ErrorBoundary를 이용해 에러가 발생했음
을 화면에 띄워주는것이 좋다고 생각합니다. 유저가 A라는 콘텐츠 이외에 다른 콘텐츠를 이용하고 있을 수 있기 때문입니다.
3. 프로젝트에서의 ErrorBoundary
상위 queryClient
- useErrorBoundary 옵션 설정export const queryClient = new QueryClient({ defaultOptions: queryConfig });
export const useQuery = <
TQueryFnData = unknown,
TError = unknown,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
options: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
): UseQueryResult<TData, TError> => {
return useQueryOrigin({
...options,
// ✅ 에러 발생 시 errorBoundary를 사용하여 에러처리할지 결정
// 기본적으로 useErrorBoundary는 false
**useErrorBoundary: !options.onError,**
suspense: !options.onError,
});
};
import client from '@/lib/client';
**import { useQuery } from '@/lib/react-query';**
import { ArticleDto } from '../types';
const getArticle = async (id: string): Promise<ArticleDto> => {
return client.get(`/api/articles/${id}`);
};
export const useArticle = ({ id }: { id: string }) => {
const { data: article, ...rest } = **useQuery**({
queryKey: ['article', id],
queryFn: () => getArticle(id),
});
if (!article) {
throw () => getArticle(id);
}
return { article, ...rest };
};
3-1. useErrorBoundary 설정에 대한 정리
useErrorBoundary
: !options.onError ⇒ (options.onError가 없을 경우에만 useErrorBoundary가 true)useErrorBoundary
가 존재하지 않으면true
로 동작합니다.useErrorBoundary
가 존재하면false
로 유지됩니다.useErrorBoundary : !options.onError
를 설정하면, 자식 API 호출 쿼리에서 별도의 useErrorBoundary 설정이 없는 경우, 상위 옵션 상속을 받습니다.import React from 'react';
interface ErrorBoundaryProps {
children: React.ReactNode;
fallback:
| React.ReactNode
| ((message: string, refresh: () => void) => React.ReactNode);
}
interface ErrorBoundaryState {
hasErr: boolean;
message: string;
}
export class ErrorBoundary extends React.Component<
ErrorBoundaryProps,
ErrorBoundaryState
> {
// ✅ constructor를 통해 초기 상태를 설정
// (초기값은 에러발생❌, 메세지는 빈 문자열)
constructor(props: ErrorBoundaryProps) {
super(props);
**this.state = { hasErr: false, message: '' };
}
// ✅ getDerivedStateFromError 메서드를 통해 에러가 발생하면
// hasErr과 message state를 업데이트
static getDerivedStateFromError(err: Error) {
return { hasErr: true, message: err.message ?? err.toString() };
}
handleRefresh = () => {
window.location.reload();
};
// ✅ render 부분에서는 업데이트된 hasErr 상태를 통해
// 정상 컴포넌트를 렌더링할지, fallback을 렌더링할지 결정한다
render() {
// ✅ 에러가 발생했을 때 대신 렌더링할 컴포넌트
const { fallback } = this.props;
// ✅ 에러가 발생하지 않았을 때 렌더링할 자식 컴포넌트
if (!this.state.hasErr) {
return this.props.children;
}
if (typeof fallback === 'function') {
return fallback(this.state.message, this.handleRefresh);
}
return fallback;
}
}
이러한 ErrorBoundary 컴포넌트
를 통해 자식 컴포넌트에서 발생하는 에러를 캐치하여 UI를 안정적으로 유지할 수 있습니다.
4. 사용하기
상위 useQuery
에서 useErrorBoundary: !options.onError
로 옵션을 설정한 상태
만들어둔 error boundary 컴포넌트
로 감싸주면 됩니다.// ✅ 메인 페이지의 "새로운 피드" 컴포넌트에 대한 에러처리
<ContentsBoxLayout>
<LandingTitle
logo={<Image path="/asset/icon/new 1.png" />}
title="새로운 피드"
extra={<MoreButton path="/article/feed" />}
/>
<ErrorBoundary fallback={<ErrorFallback />}>
<FeedList />
</ErrorBoundary>
</ContentsBoxLayout>
이 코드들에서는 에러상태
를 관리하여, 에러가 발생할 경우
에러메세지를 보여주는 컴포넌트를 렌더링해서 화면에 에러를 표시합니다.
5. 서버 점검 화면 보여주기
마찬가지로 ErrorBoundary
를 이용해 루트레벨에서 서버 점검
과 같이 페이지 전체에 에러를 보여줘야 할 경우, GlobalErrorBoundary
컴포넌트를 만들어서 점검 화면을 보여줄 수 있습니다.
class GlobalErrorBoundary {
render() {
if (!this.state.hasErr) {
return this.props.children;
}
if(서버 점검 에러 상황에 관한 코드) {
return (
// ✅ 서버 점검을 알리는 컴포넌트
<Maintenance />
)
}
return (
...
...
)
}
}
6. 다시 시도하기
단순히 에러를 캐치해서 에러 컴포넌트를 보여주는 것 이외에도, 다시 시도 버튼
을 만들어 에러 발생 시 사용자에게 직접 다시 API 호출을 시도할 권한을 부여하여 에러를 리셋할 수 있도록 처리할 수 있습니다.
errorBoundary
적용 예시