[React] Error Boundaries

jiseong·2022년 4월 15일
0

T I Learned

목록 보기
222/291

과거에는 리액트에서 컴포넌트 내부에서 에러가 발생하면 이러한 에러를 처리할 수 있는 방식을 제공해주지 않고 어플리케이션 전체를 중단시키곤 했다.

그래서 한 곳에서 발생한 오류로 인해 전체가 중단되는 문제점을 해결하기 위해 등장한 것이 Error Boundary 이다.

Error Boundary에는 하위 컴포넌트 트리의 어디에서든 발생한 에러를 catch하는 생명주기 메서드를 제공해준다.

getDerivedStateFromError 생명주기 메서드

자식 컴포넌트에서 에러가 발생되었을 때 호출되며 여기서 Error를 캡처하여 fabllack UI를 보여줄 수 있다.

static getDerivedStateFromError(): State {
  return { hasError: true };
}

render() {
  const { hasError } = this.state;
  const { children } = this.props;

  if (hasError) return fallback UI
  
  return children;
}

componentDidCatch() 생명주기 메서드

위의 getDerivedStateFromError() 생명주기 메서드와 같이 자식 컴포넌트에서 에러가 발생되었을 때 호출되는건 동일하지만 에러 정보를 기록하는 등의 side effect 용도로 사용한다.

getDervivedStateFromErrorcomponentDidCatch 차이점이 궁금하다면 Brian Vaughn 씨가 직접 답변 주신 내용을 확인하면 좋을 것 같다.

https://www.reddit.com/r/reactjs/comments/9lp0k3/new_lifecycle_method_getderivedstatefromerror/e79elpl/

나의 경우에는 해당 메서드내에서 error 종류에 따라 toast Message를 호출하는 side effect를 사용했었다.

componentDidCatch(error: Error, errorInfo: ErrorInfo) {
  if (error instanceof CustomError) error.activateHandler();
}

Error Boundary의 배치

Error Boundary의 등장이유를 생각해보면 한곳에서 발생한 에러로 인해 전체 어플리케이션을 중단시키는일을 방지하고자 생겨났기 때문에 개발자의 의도에 따라 원하는 곳에 배치할 수 있다.

나의 경우에는 Youtube API를 호출하는 컴포넌트 상위에 Error Boundary를 배치하여 에러를 처리하고 있다.

<Styled.SearchResultWrapper>
  <Styled.MainTitle>검색 결과</Styled.MainTitle>
  <ErrorBoundary fallback={<Error />}>
    <SearchResultContainer
      keyword={keyword}
      handleSelectMusic={handleSelectMusic}
      />
  </ErrorBoundary>
</Styled.SearchResultWrapper>

최종 코드

import React, { Component, ErrorInfo, ReactElement, ReactNode } from 'react';
import CustomError from '../../../utils/customError';

interface Props {
  children: ReactNode;
  fallback: ReactElement;
  isReload?: boolean;
}

interface State {
  hasError: boolean;
}

class ErrorBoundary extends Component<Props, State> {
  state: State = {
    hasError: false,
  };

  static getDerivedStateFromError(): State {
    return { hasError: true };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    if (error instanceof CustomError) error.activateHandler();
  }

  resetBoundary = () => {
    this.setState({ hasError: false });
  };

  reloadBoundary = () => window.location.reload();

  render() {
    const { hasError } = this.state;
    const { children, fallback, isReload } = this.props;

    if (hasError)
      return React.cloneElement(fallback, {
        refresh: isReload ? this.reloadBoundary : this.resetBoundary,
      });

    return children;
  }
}

export default ErrorBoundary;

Reference

0개의 댓글