๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๐Ÿ‘ฉ๐Ÿป‍๐Ÿ’ป dev

โš›๏ธ Error boundary

๐Ÿ˜ฑ ์•ฑ ๊ฐœ๋ฐœ ๋„์ค‘ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ์ง€ ์•Š๋Š”๋‹ค๋ฉด ๋จธ๋ฆฌ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์œ ์ €์—๊ฒŒ ๋ณด์—ฌ์ค„ ํ™”๋ฉด๋„ ์ƒˆํ•˜์–—๊ฒŒ ๋ณ€ํ•  ๊ฑฐ ๊ฐ™๋‹ค.
๐Ÿค” ๋ฆฌ์•กํŠธ์—์„œ๋Š” ์—๋Ÿฌ์ฒ˜๋ฆฌ๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด์ค„ ์ˆ˜ ์žˆ์„๊นŒ?

๋ฆฌ์•กํŠธ์˜ Error boundary (legacy)

๊ฐœ๋ฐœ ๋ฌธ์„œ์— ์˜ํ•˜๋ฉด ์ผ๋ฐ˜์ ์œผ๋กœ ๋ฆฌ์•กํŠธ๋Š” ๋ Œ๋”๋ง ๋„์ค‘์— ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด UI๋ฅผ ํ™”๋ฉด์—์„œ ์ œ๊ฑฐํ•œ๋‹ค.

์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํ•ด๋‹น UI๋ฅผ Error boundary๋กœ ๊ฐ์‹ธ์ค„ ์ˆ˜ ์žˆ๋‹ค.

 

์ฆ‰, Error boundary๋Š” ์—๋Ÿฌ๊ฐ€ ๋‚˜์„œ ์ œ๊ฑฐ๋œ UI๋Œ€์‹ ์— ํ™”๋ฉด์— fallback UI๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ํŠน์ˆ˜ํ•œ ์ปดํฌ๋„ŒํŠธ๋‹ค.

 

Error boundary๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” static getDerivedStateFromError๋ฅผ ์ œ๊ณตํ•ด์•ผ ํ•œ๋‹ค.

static getDerivedStateFromError๋ฅผ ์ œ๊ณตํ•ด์•ผ ์—๋Ÿฌ์— ๋”ฐ๋ผ state์„ ์—…๋ฐ์ดํŠธํ•˜๊ณ  ์œ ์ €ํ•œํ…Œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

์ถ”๊ฐ€์ ์œผ๋กœ componentDidCatch๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด ๋ถ„์„ ์„œ๋น„์Šค์— ์“ฐ์ผ ์—๋Ÿฌ ๋กœ๊น… ๋กœ์ง์„ ์ถ”๊ฐ€ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

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

  static getDerivedStateFromError(error) {
    // ๋‹ค์Œ render ์‹œ์— fallback UI๊ฐ€ ๋ณด์ด๋„๋ก state ์—…๋ฐ์ดํŠธ
    return { hasError: true };
  }

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

  render() {
    if (this.state.hasError) {
      // ์•„๋ฌด ์ปค์Šคํ…€ fallback UI๋ฅผ ๋ Œ๋”ํ•  ์ˆ˜ ์žˆ๋‹ค.
      return this.props.fallback;
    }

    return this.props.children;
  }
}

// ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ ์ผ๋ถ€๋ฅผ ๊ฐ์‹ธ์ฃผ๋ฉด ๋œ๋‹ค.
<ErrorBoundary fallback={<p>Something went wrong</p>}>
  <Profile />
</ErrorBoundary>

 

Profile ๋‚˜ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ ์—๋Ÿฌ๋ฅผ ๋˜์ง„๋‹ค๋ฉด ErrorBoundary์—์„œ๋Š” ๊ทธ ์—๋Ÿฌ๋ฅผ catch ํ•˜๊ณ ,

์ œ๊ณตํ•œ fallback UI์™€ ํ•จ๊ป˜ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ , ์—๋Ÿฌ ๊ณต์ง€ ์„œ๋น„์Šคํ•œํ…Œ production error report๋ฅผ ๋ณด๋‚ธ๋‹ค.

๐Ÿ“ ํ˜„์žฌ๋Š” ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ๋กœ error boundary๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜๋Š” ์—†๋‹ค.
ํ•˜์ง€๋งŒ ์ง์ ‘ error boundary class๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ  react-error-boundary ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ์—์„œ์˜ Error boundary

๋ฆฌ์•กํŠธ์˜ Error boundary๊ฐ€ ํ•  ์ˆ˜ ์—†๋Š” ๋ช‡ ๊ฐ€์ง€๋ฅผ ์‚ดํŽด๋ณด์ž๋ฉด

  • ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋‚ด์˜ ์—๋Ÿฌ ์บ์น˜ (try-catch ๋ธ”๋ก์œผ๋กœ ๊ฐ์‹ธ์ค˜์•ผ ํ•จ) โŒ
  • ๋น„๋™๊ธฐ ์ฝ”๋“œ (API, setTimeout, requestAnimationFrame ๋“ฑ๋“ฑ) ๋‚ด์—์„œ ์บ์น˜ โŒ
  • ์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง ๋‚ด์˜ ์—๋Ÿฌ ์บ์น˜ โŒ
  • Error boundary ์ž์ฒด ๋‚ด์˜ ์—๋Ÿฌ ์บ์น˜ โŒ
  • ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ์— ์‚ฌ์šฉ โŒ
  • ๋‚ด๋ถ€์— ํ›… ์‚ฌ์šฉ โŒ

๊ฐœ๋ฐœ ๋ฌธ์„œ์—์„œ ๋‚˜์™”๋“ฏ์ด ์ „ํ†ต์ ์ธ Error Boundary ์ปดํฌ๋„ŒํŠธ์— wrapper๋ฅผ ์“ฐ์ธ react-error-boundary๋ฅผ ํ™œ์šฉํ•ด์„œ ์œ„์˜ ์ด์Šˆ๋“ค์„ ๊ทน๋ณตํ•  ์ˆ˜ ์žˆ๋‹ค.

์‚ฌ์šฉ๋ฒ•

์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋˜๋Š” ๊ฐ๊ฐ์˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ <ErrorBoundary>๋กœ ๊ฐ์‹ธ์ค„ ์ˆ˜ ์žˆ๋‹ค.

 

๊ธฐ๋ณธ์ ์ธ ๊ตฌ์„ฑ์„ ์‚ดํŽด๋ณด์ž.

 

๋‹ค์Œ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด fallback UI๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์˜ˆ์‹œ๋‹ค.

import React from 'react';
import { ErrorBoundary } from "react-error-boundary";

const App = () => {
return <ErrorBoundary fallback={<div>Something went wrong</div>}>
/* rest of your component */
</ErrorBoundary>
}

 

๋‹ค์Œ๊ณผ ๊ฐ™์ด fallback ์ปดํฌ๋„ŒํŠธ๋กœ ์ƒ์„ธํ•œ ์—๋Ÿฌ ์‚ฌํ•ญ์„ ๋ฉ”์‹œ์ง€๋กœ ๋‚˜ํƒ€๋‚ด๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ๋‹ค.

 import React from 'react';
 import { ErrorBoundary } from "react-error-boundary";

 function fallbackRender({ error, resetErrorBoundary }) {
   // resetErrorBoundary()๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์—๋Ÿฌ ๋ฐ”์šด๋”๋ฆฌ๋ฅผ resetํ•˜๊ณ  ๋žœ๋”๋ง์„ ์žฌ์‹œ๋„ ํ•œ๋‹ค.
   return (
     <div role="alert">
       <p>Something went wrong:</p>
       <pre style={{ color: "red" }}>{error.message}</pre>
     </div>
   );
 }

 const App = () => {
 return <ErrorBoundary 
 fallbackRender={fallbackRender}
 onReset={(details) => {
 	 // ์•ฑ์˜ state์„ ๋ฆฌ์…‹ํ•ด์„œ ์—๋Ÿฌ๊ฐ€ ๋‹ค์‹œ ๋ฐœ์ƒํ•˜๋Š” ๊ฑธ ๋ง‰๊ธฐ
   }}
 >
 /* rest of your component */
 </ErrorBoundary>
 }

 

๋˜๋Š” fallback์ด๋‚˜ fallbackRender ๋Œ€์‹  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ๋‹ค.

 import React from 'react';
 import { ErrorBoundary } from "react-error-boundary";

 const Fallback = ({ error, resetErrorBoundary }) => {
   // Call resetErrorBoundary() to reset the error boundary and retry the render.
   return (
     <div role="alert">
       <p>Something went wrong:</p>
       <pre style={{ color: "red" }}>{error.message}</pre>
     </div>
   );
 }

 const App = () => {
 return <ErrorBoundary 
 FallbackComponent={Fallback}
 onReset={(details) => {
     // Reset the state of your app so the error doesn't happen again
   }}
 >
 /* rest of your component */
 </ErrorBoundary>
 }

 

๋งŒ์•ฝ ์—๋Ÿฌ ๋กœ๊ทธ๋ฅผ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ถ”๊ฐ€ํ•ด ์ค„ ์ˆ˜ ์žˆ๋‹ค.

 import React from 'react';
 import { ErrorBoundary } from "react-error-boundary";

 const logError = (error: Error, info: { componentStack: string }) => {
   // Do something with the error, e.g. log to an external API
 };

 const Fallback = ({ error, resetErrorBoundary }) => {
   // Call resetErrorBoundary() to reset the error boundary and retry the render.
   return (
     <div role="alert">
       <p>Something went wrong:</p>
       <pre style={{ color: "red" }}>{error.message}</pre>
     </div>
   );
 }
 // You can use fallback / fallbackRender / FallbackComponent anything
 const App = () => {
 return <ErrorBoundary 
 FallbackComponent={Fallback}
 onError={logError}
 onReset={(details) => {
     // Reset the state of your app so the error doesn't happen again
   }}
 >
 /* rest of your component */
 </ErrorBoundary>
 }

 

๋˜๋Š” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋‚˜ ๋น„๋™๊ธฐ ์ฝ”๋“œ์— ์—๋Ÿฌ๋ฅผ ์บ์น˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

 import { useErrorBoundary } from "react-error-boundary";

 function Example() {
   const { showBoundary } = useErrorBoundary();
   const getGreeting = async(name) => {
     try {
         const response = await fetchGreeting(name);
         // rest of your code
     } catch(error){
          // Show error boundary
         showBoundary(error);
     }
   }
   useEffect(() => {
    getGreeting()
   });

   return <Whatever UI you want to render/>
 }

 

๋‹ค๋งŒ ErrorBoundary๋Š” ํด๋ผ์ด์–ธํŠธ ์ชฝ ์ปดํฌ๋„ŒํŠธ๋‹ค.

๋”ฐ๋ผ์„œ nextjs์—์„œ ์‚ฌ์šฉ ์‹œ 'use client'์„ ํŒŒ์ผ ์ตœ์ƒ๋‹จ์— ์–ธ๊ธ‰ํ•ด ์ฃผ๊ฑฐ๋‚˜,

serializeable (๋ฐ”์ดํŠธ ์ŠคํŠธ๋ฆผ์œผ๋กœ ํ”„๋กญ์Šค ํ˜•ํƒœ๋ฅผ ์ž์œ ๋กญ๊ฒŒ ์ „ํ™˜์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ํŠน์„ฑ, js์—์„œ๋Š” JSON.stringify()์™€ JSON.parse()๋กœ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค) props๋งŒ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿ“š References

https://medium.com/@vnkelkar11/using-error-boundary-in-react-a29ded725eee

 

Mastering Error Boundaries in React: A Guide to Effective Error Handling

What is an Error Boundary?

medium.com

https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary

 

Component – React

The library for web and native user interfaces

react.dev