React Error Handling(feat. react-query, axios)

김현조·2023년 3월 26일
19

FrontEnd

목록 보기
5/9
post-thumbnail

에러란?

프로그램 실행 중 어떤 원인에 의해 오작동하거나 비정상 종료된 경우,

이를 초래하는 원인을 프로그램 에러 또는 오류라 한다.

에러, 버그, 예외 등은 대개 비슷하게 예상치 못한 문제 정도의 의미로 사용되는데 이를 구분해보면 내가 핸들링하고자 하는 대상이 무엇인지 알 수 있다.

  • 에러: 주로 프로그램 사용자나 네트워크 환경 등 외부 요인에 의해 발생하는 문제 (입력란에 이상한 문자열 등)
  • 버그: 프로그래머의 실수에 의해 발생하는 문제 (오버플로우, NULL 포인터 호출 등)
  • 예외: 런타임에 자주 발생하며 예방하기 어려움 (손상된 파일 열기, 더이상 존재하지 않는 DB에 연결 시도 등)

여기서 지금 다루고자 하는 것은 “에러”이다. 어떻게 하면 에러를 잘 다루어 궁극적으로 완성도 높은 프로그램을 만들 수 있을까? 이를 프론트엔드의 관점에서 알아보도록 하자.

프론트엔드에서 자주 발생하는 에러

프론트엔드에서 자주 발생하는 에러에는 다음과 같은 종류가 있다.

  • 네트워크 에러
    • 브라우저와 서버 간의 네트워크 연결 문제
    • Request timeout, DNS error 등
  • 사용자 입력 에러
    • 사용자가 입력창에 invalid한 값을 입력한 경우
    • Invalid한 email 입력, 필수 입력 필드 입력 안함 등

이외에도 인증 에러, 브라우저 에러, OS 업데이트에 의한 에러, 잘못된 접근으로 인한 에러, 악의적 목적의 접근에 의한 에러 등이 있을 수 있다.

에러 핸들링이란?

에러가 발생할 수도 있는 지점을 예외 구문으로 설정하여 프로그램이 예상치 못하게 종료되거나 기능을 못하는 상황을 방지

좋은 에러 핸들링을 위해 해야할 것

에러 핸들링을 위해 프론트엔드에서는 다음과 같은 방법들을 사용할 수 있다.

  • User-Friendly한 에러 메시지 보여주기
    에러가 발생했을 때 “error code: 293”과 같은 문구가 나온다면 사용자 경험도 떨어지고 대처도 어렵다. 따라서 에러 발생 이유나 사용자의 다음 행동을 말해주는 것이 좋다. ex) “이메일을 입력해주세요.”
  • 에러 로깅하기
    Sentry 등의 도구를 사용하여 에러가 발생했을 때 디버깅이 용이하도록 로깅해놓는 것도 좋은 방법이다. 이를 통해 에러 발생 패턴을 파악하고 미연에 방지할 수도 있다.

이러한 에러 핸들링을 위해서는 프론트엔드단에서 적절한 상황에 적절한 대처를 할 수 있도록 설정해주어야 한다. 하지만 팀 프로젝트에서 여러 개발자가 각자의 방식으로 에러를 처리하게 되면 또는 전혀 하지 않게 되면 유지보수하기 정말 어려울 뿐더러 어디서 에러가 날지 찾기 어려운 코드가 된다. 따라서 일관성있는 에러 핸들링은 서비스 완성도에 크게 기여한다.

일관성있는 프론트엔드 에러 핸들링

그렇다면 위의 목표를 위해 어떻게 하는 것이 좋을까?

필자는 아래와 같은 방법을 사용했다.

  1. 각각의 기능에서 발생 가능한 에러와 status code를 정의한다.

  2. 정의에 따라 React-query의 onError 프로퍼티를 통해 처리한다.

    const { mutate } = useMutation(uploadImage, {
        onError: (err: { response: { status: number } }) => {
          if (err.response.status === 413) {
            toast.error('이미지 사이즈가 너무 커요. 다른 사진으로 다시 시도해주세요. ');
          } else {
            toast.error('이미지 업로드에 실패했어요. 다시 시도해주세요.');
          }
        },
      });
  3. 서비스 내에서 throw 되었거나 예상 불가한 에러는 ErrorBoundary에 잡히도록 감싼다.

    import React from 'react';
    
    interface IErrorBoundaryState {
      hasError: boolean;
    }
    
    export class ErrorBoundary extends React.Component<any, IErrorBoundaryState> {
      constructor(props: any) {
        super(props);
        this.state = { hasError: false };
      }
    
      static getDerivedStateFromError(error: Error) {
        return { hasError: true };
      }
    
      render() {
        if (this.state.hasError) {
          return <h1>문제가 발생했어요.</h1>;
        }
    
        return this.props.children;
      }
    }
  4. 이외의 서버관련 에러는 axios의 interceptor를 활용해 처리한다.

     apiClient.interceptors.response.use(
      (res) => res.data,
      (err) => {
        const { status } = err.response;
        if (status >= 500) {
          toast.error('앗! 오류가 발생했어요.' + '\n' + '잠시 후에 다시 시도해보세요.');
        }
      },
    );

결론

React에서의 에러 핸들리에 대해 조사하다보니 다양한 설계와 케이스들이 존재했다. 내 프로그램에는 어떤 방식이 적절할지 고민해보고 점차 발전시켜나가면 좋을 것 같다.

출처

2개의 댓글

comment-user-thumbnail
2023년 3월 29일

좋은 글이라 재밌게 보고 있었는데
작성자분이 현조 님!!
저는 익명의 팬입니다!
벨로그로 오셨군요 흐흐
저도 최근에 react query 에서 에러 핸들링 작업하고 있어서 더 집중해서 봤네요!
늘 응원합니다!!

1개의 답글