๐Ÿ“ฆ ์žก๋™์‚ฌ๋‹ˆ

ํ•˜๋‚˜์˜ ํ‚ค์›Œ๋“œ๋ฅผ ์žก๊ณ  ์ข€ ํŽธํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๊ณ  ์‹ถ์–ด ๋งŒ๋“  ์žก๋™์‚ฌ๋‹ˆ

์žก๋™์‚ฌ๋‹ˆ๋Š” ์กฐ์„  ํ›„๊ธฐ ํ•™์ž ์•ˆ์ •๋ณต์ด ํŽธ์ฐฌํ•œ ์žก๋™์‚ฐ์ด(้›œๅŒๆ•ฃ็•ฐ)์—์„œ ์œ ๋ž˜๋œ ๋ง์ด๋‹ค.
์žก๋™์‚ฐ์ด๋Š” ์žก๊ธฐ(้›œ่จ˜)์˜ ํ˜•ํƒœ๋ฅผ ๋นŒ๋ ค์˜จ ์ฑ…์œผ๋กœ ๊ตฌ์ฒด์ ์ธ ์ฒด๊ณ„๊ฐ€ ์žกํ˜€์žˆ์ง€ ์•Š์€ ํ˜•์‹์ด๋‹ค.
ํ•ญ๋ชฉ์ด ๋‹ค์†Œ ๋‚œ์žกํ•˜๊ณ  ๋‚ด์šฉ์˜ ๊ตฌ๋ถ„์ด ํ˜ผ๋™๋˜์–ด์žˆ๋‹ค๊ณ  ํ•œ๋‹ค. ๐Ÿคฃ

๐Ÿ—‚๏ธ Error Boundary

Error Boundary๋ž€ ์—๋Ÿฌ์— ๋Œ€ํ•œ ๊ฒฝ๊ณ„๋ฅผ ์˜๋ฏธํ•œ๋‹ค. ์ฆ‰, ํŠน์ • Error Boundary๋กœ ๊ฐ์‹ธ์—ฌ์ง„ ๊ตฌ๊ฐ„์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๊ทธ ์—๋Ÿฌ๋ฅผ ์žก์•„๋‚ด์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ์ด์— ๋Œ€ํ•œ ๊ฐœ๋…์„ ์ ์šฉํ•œ Error Boundary ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฆฌ์•กํŠธ์—์„œ๋Š” ๊ณต์‹๋ฌธ์„œ๋ฅผ ํ†ตํ•ด ์ œ๊ณตํ•˜๊ณ  ์žˆ๋‹ค. ๋จผ์ € ๋ฆฌ์•กํŠธ ๊ณต์‹๋ฌธ์„œ์—์„œ ์„ค๋ช…ํ•˜๋Š” Error Boundary๋ฅผ ์‚ดํŽด๋ณด์ž.

์•„๋ž˜๋Š” ๋ฆฌ์•กํŠธ ๊ณต์‹๋ฌธ์„œ๋ฅผ ๋ฒˆ์—ญํ•˜์—ฌ ์ •๋ฆฌํ•œ ๋‚ด์šฉ์ด๋‹ค.

๋ฆฌ์•กํŠธ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋ Œ๋”๋ง ์ค‘์— ์—๋Ÿฌ๋ฅผ ๋˜์ง„๋‹ค๋ฉด ํ™”๋ฉด์œผ๋กœ๋ถ€ํ„ฐ UI๋ฅผ ์ง€์šธ ๊ฒƒ์ด๋‹ค. ์ด๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ปดํฌ๋„ŒํŠธ๋“ค์„ Error Boundary๋กœ ๊ฐ์‹ธ์•ผํ•œ๋‹ค. Error Boundary๋Š” ํŠน๋ณ„ํ•œ ์ปดํฌ๋„ŒํŠธ์ด๋‹ค. ์ด ์ปดํฌ๋„ŒํŠธ๋Š” ์—๋Ÿฌ๋กœ ์ธํ•œ ์ถฉ๋Œ์ผ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ, ์—๋Ÿฌ๋ฅผ ์žก์•„๋‚ด์„œ fallback UI๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋‹ค.

Error Boundary๋Š” ํด๋ž˜์Šค ์ปดํฌ๋„ŒํŠธ๋กœ ๊ตฌํ˜„๋œ๋‹ค. ์ด ๋•Œ, static getDerivedStateFromError() ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•ด์•ผ ํ•œ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ, ์—๋Ÿฌ์— ๋Œ€ํ•œ ์‘๋‹ต์œผ๋กœ state๋ฅผ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ ์„ ํƒ์ ์œผ๋กœ componentDidCatch()๋ฅผ ํ†ตํ•ด์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ, ์—๋Ÿฌ๋ฅผ ๊ธฐ๋กํ•˜๊ฑฐ๋‚˜ ์—๋Ÿฌ ๋ฆฌํฌํŒ… ์„œ๋น„์Šค์— ์—๋Ÿฌ๋ฅผ ๊ธฐ๋กํ•  ์ˆ˜ ์žˆ๋‹ค.(์—๋Ÿฌ์— ๊ด€ํ•œ ์–ด๋–ค ํ–‰์œ„๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.)

์•„๋ž˜๋Š” Error Boundary๋ฅผ ๊ตฌํ˜„ํ•œ ์˜ˆ์‹œ์ด๋‹ค.

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // optional
    // Example "componentStack":
    //   in ComponentThatThrows (created by App)
    //   in ErrorBoundary (created by App)
    //   in div (created by App)
    //   in App
    // ex) logErrorToMyService(error, info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return this.props.fallback;
    }

    return this.props.children;
  }
}

ํ˜„์žฌ๋Š” ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ๋กœ Error Boundary๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์—†๋‹ค. ๋งŒ์•ฝ ๋งค๋ฒˆ ์ง์ ‘ ์œ„์˜ class component๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์‹ถ์ง€ ์•Š๋‹ค๋ฉด react-error-boundary ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

๐Ÿ—‚๏ธ Error Boundary ์ ์šฉํ•ด๋ณด๊ธฐ

Error Boundary๋ฅผ ์ ์šฉํ•ด๋ณด๊ธฐ ์œ„ํ•ด ์•„์ฃผ์•„์ฃผ ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์•˜๋‹ค.

App.jsx

import { ErrorBoundary } from './ErrorBoundary';
import { Temp } from './Temp';

export default function App() {
  return (
    <div className="App">
      <ErrorBoundary>
        <h1>Hello CodeSandbox</h1>
        <h2>Start editing to see some magic happen!</h2>
        <Temp />
      </ErrorBoundary>
    </div>
  );
}

ErrorBoundary.jsx

import React from 'react';

export class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

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

  componentDidCatch(error, info) {
    console.log('error: ', error);
    console.log('info: ', info);
  }

  render() {
    if (this.state.hasError) {
      return <div>์—๋Ÿฌ์ž…๋‹ˆ๋‹ค!!!</div>;
    }

    return this.props.children;
  }
}

Temp.jsx

export const Temp = () => {
  throw new Error('๊ณ ์˜์ ์ธ ์—๋Ÿฌ ๋ฐœ์ƒ!');

  return <div>Temp Component์ž…๋‹ˆ๋‹ค.</div>;
};

์œ„์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ํ™”๋ฉด์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

error-boundary-1

๊ทธ๋ฆฌ๊ณ  ์ด ์—๋Ÿฌ iframe์„ ๋‹ซ์œผ๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด fallback UI๊ฐ€ ๋ Œ๋”๋ง๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

error-boundary-2

์œ„์™€ ๊ฐ™์ด ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ client ๋‹จ์˜ ์—๋Ÿฌ๊ฐ€ ์•„๋‹ˆ๋ผ ์„œ๋ฒ„์™€์˜ ํ†ต์‹ ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ๋ผ๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ?

๐Ÿ—‚๏ธ Error Boundary์˜ ํ•œ๊ณ„

์œ„์˜ ์˜ˆ์ œ์—์„œ Temp ์ปดํฌ๋„ŒํŠธ์—์„œ ์˜๋„์ ์œผ๋กœ ๋น„๋™๊ธฐ ํ†ต์‹ ์— ๋Œ€ํ•œ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ๋ณด์ž.

Temp.jsx

import { useEffect } from 'react';

export const Temp = () => {
  useEffect(() => {
    async function temp() {
      throw new Error('๊ณ ์˜์ ์ธ ์—๋Ÿฌ ๋ฐœ์ƒ!');
    }
    temp();
  }, []);
  return <div>Temp Component์ž…๋‹ˆ๋‹ค.</div>;
};

๊ทธ๋Ÿผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์—๋Ÿฌ iframe์ด ๋ณด์ธ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ด๋ฅผ ๋‹ซ์œผ๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ๊ทธ๋ƒฅ ์ •์ƒ์ ์ธ ํ™”๋ฉด์ด ๋ Œ๋”๋ง๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

error-boundary-3

๋ถ„๋ช…ํžˆ ์—๋Ÿฌ๋Š” ๋ฐœ์ƒํ–ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์™œ ์šฐ๋ฆฌ๊ฐ€ ์ „๋‹ฌํ•œ fallback UI๊ฐ€ ์•„๋‹ˆ๋ผ ์ •์ƒ์ ์ธ ํ™”๋ฉด์ด ๋ Œ๋”๋ง๋˜์–ด์žˆ๋Š” ๊ฒƒ์ผ๊นŒ???

2๊ฐ€์ง€๋ฅผ ์ƒ๊ฐํ•ด๋ณผ ์ˆ˜ ์žˆ๋‹ค.

  1. Error Boundary์˜ ์„ค๋ช…์„ ๋‹ค์‹œ ์‚ดํŽด๋ณด์ž. Error Boundary๋Š” ๋ Œ๋”๋ง ์ค‘์— ์—๋Ÿฌ๋ฅผ ์žก์•„๋‚ธ๋‹ค๊ณ  ํ–ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์šฐ๋ฆฌ๊ฐ€ ๋ฐœ์ƒ์‹œํ‚จ ์—๋Ÿฌ๋Š” ๋ Œ๋”๋ง ์ค‘์ด ์•„๋‹Œ ๋น„๋™๊ธฐ ํ†ต์‹  ์ค‘์— ๋ฐœ์ƒํ•œ ์—๋Ÿฌ์ด๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— Error Boundary๊ฐ€ ์žก์•„๋‚ผ ์ˆ˜ ์—†๋Š” ๊ฒƒ์ด๋‹ค.

  2. useEffect๋Š” ๋น„๋™๊ธฐ ํ†ต์‹ ์„ ์œ„ํ•œ ํ›…์ด๋ผ๊ธฐ๋ณด๋‹จ, ๋ Œ๋”๋ง์ด ๋๋‚œ ํ›„์— ์‹คํ–‰๋˜๋Š” ํ›…์ด๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— useEffect ๋‚ด๋ถ€์—์„œ ๋ฐœ์ƒํ•œ ์—๋Ÿฌ๋Š” Error Boundary๊ฐ€ ์žก์•„๋‚ผ ์ˆ˜ ์—†๋Š” ๊ฒƒ์ด๋‹ค.(๋ Œ๋”๋ง ์ค‘์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์—)

๊ทธ๋ ‡๋‹ค๋ฉด ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ? ๊ฐ„๋‹จํ•˜๊ฒŒ ๋“œ๋Š” ์ƒ๊ฐ์€ ์–ด๋–ค ์ƒํƒœ๋ฅผ ๋‘๊ณ  ๋น„๋™๊ธฐ ํ†ต์‹ ์ด ์‹คํŒจํ–ˆ์„ ๋•Œ, ๊ทธ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์ƒํƒœ๋ฅผ Error Boundary์—์„œ ๊ฐ์ง€ํ•˜์—ฌ fallback UI๋ฅผ ๋ Œ๋”๋งํ•˜๋ฉด ๋  ๊ฒƒ์ด๋‹ค.

์•„๋ž˜์™€ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•ด๋ณผ ์ˆ˜ ์žˆ๋‹ค.

Temp.jsx

import { useEffect, useState } from 'react';

export const Temp = () => {
  const [isError, setIsError] = useState(false);
  useEffect(() => {
    async function temp() {
      try {
        throw new Error('๊ณ ์˜์ ์ธ ์—๋Ÿฌ ๋ฐœ์ƒ!');
      } catch (error) {
        setIsError(true);
      }
    }
    temp();
  }, []);

  if (isError) throw new Error('์—ฌ๊ธฐ๋„ ๊ณ ์˜์ ์ธ ์—๋Ÿฌ ๋ฐœ์ƒ!');
  return <div>Temp Component์ž…๋‹ˆ๋‹ค.</div>;
};

์œ„์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•˜๋ฉด Error Boundary๋ฅผ ํ†ตํ•ด fallback UI๊ฐ€ ์ž˜ ๋ Œ๋”๋ง๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ์—ฌ๊ธฐ๊นŒ์ง€๋งŒ ๋ณด๋ฉด ๋ญ”๊ฐ€ ๊ทธ๋Ÿด์‹ธํ•˜์ง€๋งŒ, ์–ด๋Š์ •๋„ ๋ฆฌ์•กํŠธ๋ฅผ ๊ฐœ๋ฐœํ•ด๋ดค๋‹ค๋ฉด ๋ญ”๊ฐ€ ์ด์ƒํ•˜๋‹ค..? ์‹ถ์„ ๊ฒƒ์ด๋‹ค. ์œ„์˜ ๋กœ์ง์„ ์ž์„ธํžˆ ์‚ดํŽด๋ณด๋ฉด try, catch ๊ตฌ๋ฌธ์„ ํ†ตํ•ด์„œ ์—๋Ÿฌ๋ฅผ ์ž˜ ์žก์•„๋†“๊ณ  ๋‹ค์‹œ throw๋ฅผ ํ†ตํ•ด ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ณ  ์žˆ๋‹ค. ์ด ํ๋ฆ„์ด ์–ด๋”˜๊ฐ€ ์–ด์ƒ‰ํ•˜๊ณ  ๋ถˆํ•„์š”ํ•˜๋‹ค๊ณ  ๋Š๊ปด์ง€์ง€์•Š๋Š”๊ฐ€? ํ•ด์„œ ๋ฆฌ์•กํŠธ์˜ ๊ตฌ๋ฒ„์ „ ๊ณต์‹๋ฌธ์„œ๋ฅผ ์‚ดํŽด๋ณด์•˜๋‹ค.

๐Ÿ—‚๏ธ Error Boundary์˜ ํ•œ๊ณ„(feat. ๋ฆฌ์•กํŠธ ๊ตฌ๋ฒ„์ „)

๋ฆฌ์•กํŠธ์˜ ๊ตฌ๋ฒ„์ „ ๊ณต์‹๋ฌธ์„œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ๋‚˜์™€์žˆ๋‹ค.

๊ณผ๊ฑฐ์—๋Š” ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋กœ์ง ์—๋Ÿฌ๊ฐ€ React์˜ ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ํ›ผ์†ํ•˜๊ณ  ์•ฑ์„ ๋ถˆ์•ˆ์ •ํ•˜๊ฒŒ ๋งŒ๋“ค์—ˆ๋‹ค. ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด React 16๋ถ€ํ„ฐ๋Š” ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋กœ์ง ์—๋Ÿฌ๊ฐ€ React์˜ ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ํ›ผ์†ํ•˜์ง€ ์•Š๋„๋ก ์—๋Ÿฌ ๊ฒฝ๊ณ„(Error Boundary)๋ฅผ ๋„์ž…ํ–ˆ๋‹ค.(๋˜ํ•œ React 16๋ถ€ํ„ฐ๋Š” ๋ Œ๋”๋ง ์ค‘ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ „์ฒด ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ์˜ ๋งˆ์šดํŠธ๊ฐ€ ํ•ด์ œ๋œ๋‹ค.)

Error Boundary๋Š” ์•„๋ž˜์˜ ์—๋Ÿฌ๋Š” ํฌ์ฐฉํ•˜์ง€ ์•Š๋Š”๋‹ค.

  • ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ(์˜ˆ๋ฅผ ๋“ค์–ด, onClick ์†์„ฑ์œผ๋กœ ์ „๋‹ฌ๋œ ํ•จ์ˆ˜)
  • ๋น„๋™๊ธฐ ์ฝ”๋“œ(์˜ˆ๋ฅผ ๋“ค์–ด, setTimeout ๋˜๋Š” requestAnimationFrame ์ฝœ๋ฐฑ ํ•จ์ˆ˜, ์„œ๋ฒ„ ํ†ต์‹  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์ฝœ๋ฐฑ ํ•จ์ˆ˜)
  • ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง
  • ์ž์‹ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์•„๋‹Œ Error Boundary ์ž์ฒด์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ

์ฆ‰, Error Boundary๋Š” ๊ทธ์ € ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ๊ฐ€ ๋ Œ๋”๋ง๊ณผ ๊ด€๋ จ๋œ ์—๋Ÿฌ๋ฅผ ํฌ์ฐฉํ•˜๊ณ  ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋œ๋‹ค. ์ด ๋ณธ์—ฐ์˜ ์ทจ์ง€์— ๋งž๊ฒŒ ๋ฐ”๋กœ ์œ„์˜ Temp.jsx๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

Temp.jsx

import { useEffect, useState } from 'react';

export const Temp = () => {
  const [isError, setIsError] = useState(false);
  useEffect(() => {
    async function temp() {
      try {
        throw new Error('๊ณ ์˜์ ์ธ ์—๋Ÿฌ ๋ฐœ์ƒ!');
      } catch (error) {
        setIsError(true);
      }
    }
    temp();
  }, []);

  if (isError) return <div>์—๋Ÿฌ์ž…๋‹ˆ๋‹ค!!!</div>;

  return <div>Temp Component์ž…๋‹ˆ๋‹ค.</div>;
};

์œ„์˜ ์ฝ”๋“œ๊ฐ€ ์ผ๋ฐ˜์ ์ธ fetch, axios๋ฅผ ํ†ตํ•œ ์ปค์Šคํ…€ ํ›…์˜ isError๋ฅผ ํ†ตํ•œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•์ด๊ณ  tanstack/react-query์˜ useQuery๋ฅผ ํ†ตํ•œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•์ด๋‹ค. ์ด ๋ฐฉ๋ฒ•์ด Error Boundary๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ๋” ๊น”๋”ํ•˜๊ณ , ๋”์šฑ ๋” ํšจ์œจ์ ์ด๋‹ค.

๐Ÿ—‚๏ธ (์ถ”๊ฐ€) react-router์—์„œ์˜ route ์—๋Ÿฌ ์ฒ˜๋ฆฌ

react-router์—์„œ๋Š” errorElement๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ๊ฒฝ๋กœ์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ, ํŠน์ • ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ๋‹ค. react-router-dom์˜ ๊ณต์‹๋ฌธ์„œ์™€ github ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

let router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    children: [
      {
        path: '',
        element: <Outlet />,
        errorElement: <RootErrorBoundary />, // RootErrorBoundary ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ†ตํ•ด ์—๋Ÿฌ๋ฅผ ์žก์•„๋‚ธ๋‹ค.
        children: [
          {
            path: 'projects/:projectId',
            element: <Project />,
            errorElement: <ProjectErrorBoundary />, // ProjectErrorBoundary ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ†ตํ•ด ์—๋Ÿฌ๋ฅผ ์žก์•„๋‚ธ๋‹ค.
            loader: projectLoader,
          },
        ],
      },
    ],
  },
]);

export default function App() {
  return <RouterProvider router={router} fallbackElement={<Fallback />} />;
}

RootErrorBoundary.jsx

export function RootErrorBoundary() {
  let error = useRouteError() as Error;
  return (
    <div>
      <h1>Uh oh, something went terribly wrong ๐Ÿ˜ฉ</h1>
      <pre>{error.message || JSON.stringify(error)}</pre>
      <button onClick={() => (window.location.href = "/")}>
        Click here to reload the app
      </button>
    </div>
  );
}

ProjectErrorBoundary.jsx

export function ProjectErrorBoundary() {
  let error = useRouteError();

  // We only care to handle 401's at this level, so if this is not a 401
  // ErrorResponse, re-throw to let the RootErrorBoundary handle it
  if (!isRouteErrorResponse(error) || error.status !== 401) {
    throw error;
  }

  return (
    <>
      <h1>You do not have access to this project</h1>
      <p>
        Please reach out to{' '}
        <a href={`mailto:${error.data.contactEmail}`}>{error.data.contactEmail}</a> to obtain
        access.
      </p>
    </>
  );
}

๐Ÿš€ ์ •๋ฆฌ

๊ฒฐ๊ตญ React์˜ Error Boundary๋Š” ๋ Œ๋”๋ง ๊ณผ์ •์—์„œ ์ผ์–ด๋‚˜๋Š”, ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์—๋Ÿฌ๋ฅผ ํ•ธ๋“ค๋งํ•˜๊ธฐ ์œ„ํ•จ์ด๊ณ  ์„œ๋ฒ„ ํ†ต์‹ ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ๋Š” ๊ฒฐ๊ตญ ๋น„๋™๊ธฐ ํ†ต์‹ ์„ ์œ„ํ•œ ํ›…์„ ํ†ตํ•ด ํ•ธ๋“ค๋งํ•ด์•ผํ•œ๋‹ค. ๋˜, react-router๋ฅผ ํ†ตํ•œ routing์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ(ex. ์œ ์ €๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š” path๋ฅผ ํ†ตํ•ด ์ ‘๊ทผํ•˜๋Š” ๊ฒฝ์šฐ)๋Š” errorElement์™€ useRouteError๋ฅผ ํ†ตํ•ด ๋ผ์šฐํ„ฐ ๋ณ„๋กœ ์—๋Ÿฌ ๋ฐ”์šด๋”๋ฆฌ๋ฅผ ๋งŒ๋“ค์–ด์„œ ํ•ธ๋“ค๋งํ•˜๋ฉด ๋œ๋‹ค!

์ฐธ๊ณ