به طور کلی، خطاها هنگام ساخت برنامه‌ها اجتناب‌ناپذیر هستند و ممکن است از مشکلات سرور، edge caseها یا بسیاری از دلایل دیگر ناشی شوند. روش‌های زیادی برای مدیریت آن‌ها وجود دارد. یکی از این روش‌ها استفاده از error boundaryها در React است. در این مقاله قصد داریم تا مدیریت خطا در React با استفاده از کتابخانه react-error-boundary را بررسی کنیم. تا پایان این مقاله، به درک بهتری از نحوه پیاده‌سازی error boundaryها در برنامه‌های React و نحوه مدیریت موثر خطاهای synchronous و asynchronous دست پیدا خواهیم کرد.

Error Boundaryها در React

Error Boundaryهای React یکی از جنبه‌های مهم مدیریت خطا در برنامه‌های React هستند. آن‌ها کامپوننت‌های React می‌باشند که خطاهای جاوااسکریپت را در هر کجای درخت کامپوننت child خود می‌گیرند، آن‌ها را ثبت می‌کنند و به جای درخت کامپوننتی که دچار مشکل شده است، یک fallback UI نمایش می‌دهند. می‌توانیم error boundaryها را مانند یک بلاک catch {} جاوااسکریپت، اما برای کامپوننت‌ها در نظر بگیریم.

ما می‌توانیم error boundaryها را در اطراف کل برنامه یا حتی برای کنترل دقیق‌تر، در اطراف کامپوننت‌های جداگانه تنظیم نماییم. باید به این نکته توجه داشته باشیم که error boundaryها در حین رندر شدن در متدهای lifecycle و در constructorهای کل درخت زیر آن‌ها، خطاها را catch می‌کنند. با این حال، error boundaryها خطاها را برای موارد زیر دریافت نمی‌کنند:

Error Boundaryها در نسخه ۱۶ React معرفی شدند، و برای استفاده از آن‌ها، باید یک کلاس کامپوننت را با یک یا هر دو متد lifecycle زیر تعریف کنیم: getDerivedStateFromError() یا componentDidCatch():

در ادامه یک مثال ساده از یک کلاس کامپوننت داریم که هر دو متد getDerivedStateFromError() و componentDidCatch() را پیاده‌سازی می‌کند:

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, errorInfo) {
    // You can also log the error to an error reporting service
    console.log(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

// Usage in a component
class App extends React.Component {
  render() {
    return (
      <ErrorBoundary>
        <MyComponent />
      </ErrorBoundary>
    );
  }
}

در این مثال، اگر خطایی در MyComponent ایجاد شود، توسط ErrorBoundary شناسایی می‌شود، در کنسول ثبت می‌گردد و سپس به جای درخت کامپوننتی که خطا را نشان می‌دهد، پیغام "Something went wrong" به کاربر نمایش داده می‌شود.

استفاده از کتابخانه react-error-boundary

در حالی که کلاس کامپوننت‌ها و متدهای lifecycle آن‌ها می‌توانند به ما در پیاده‌سازی error boundaryها کمک کنند، اما react-error-boundary کتابخانه‌ای است که این فرآیند را ساده‌تر و کاربرپسندتر می‌کند. react-error-boundary یک کتابخانه کوچک است که روشی انعطاف‌پذیر برای مدیریت خطا جاوااسکریپتی در کامپوننت‌های React ارائه می‌دهد.

کتابخانه react-error-boundary از رویکرد مدرن‌تری همراه با هوک‌های React و کامپوننت‌های فانکشنال استفاده می‌کند که با روندهای فعلی در توسعه React هماهنگی بهتری دارد. این کتابخانه از یک کامپوننت ساده به نام ErrorBoundary استفاده می‌کند که می‌توانیم کدهای احتمالی مستعد خطا را درون آن قرار دهیم.

بررسی کامپوننت ErrorBoundary در کتابخانه react-error-boundary

کامپوننت ErrorBoundary یک prop به نام fallbackRender (یا fallbackUI) دارد که یک تابع یا یک المنت React را می‌گیرد تا در صورت بروز خطا، آن را به کاربران نمایش دهد. همچنین یک prop resetKeys ارائه می‌کند که می‌تواند برای تنظیم مجدد state کامپوننت در هنگام تغییر المنت‌های خاص مورد استفاده قرار بگیرد.

مزیت react-error-boundary این است که نیاز به نوشتن دستی کلاس کامپوننت‌ها و مدیریت state را از بین می‌برد و تمام کارهای سنگین را در پشت صحنه انجام می‌دهد.

اکنون مثالی را بررسی می‌کنیم تا ببینیم چگونه می‌توانیم از react-error-boundary در یک کامپوننت استفاده کنیم:

import { ErrorBoundary } from 'react-error-boundary'

function MyFallbackComponent({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  )
}

function MyComponent() {
  // Some component logic that may throw JS errors
}

function App() {
  return (
    <ErrorBoundary
      FallbackComponent={MyFallbackComponent}
      onReset={() => {
        // reset the state of your app here
      }}
      resetKeys={['someKey']}
    >
      <MyComponent />
    </ErrorBoundary>
  )
}

در این مثال، هر زمان که ErrorBoundary خطایی را دریافت کند، MyFallbackComponent نمایش داده می‌شود. این کامپوننت پیغام خطا را نمایش می‌دهد و دکمه‌ای را برای ریست کردن state خطا و تلاش مجدد کامپوننت ارائه می‌کند. prop onReset برای پاک کردن هر گونه side effectای که قبل از ایجاد خطا رخ داده است استفاده می‌شود، و prop resetKeys برای کنترل زمانی که state کامپوننت ریست می‌شود مورد استفاده قرار می‌گیرد.

ErrorBoundary همچنین دارای یک prop onError می‌باشد، که تابعی است که هر زمان که خطا رخ دهد فراخوانی می‌شود. می‌توانیم از این prop برای ثبت خطاها در یک سرویس گزارش خطا استفاده کنیم. به عنوان مثال:

...

// Error logging function
function logErrorToService(error, info) {
  // Use your preferred error logging service
  console.error("Caught an error:", error, info);
}

// App component
function App() {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback} onError={logErrorToService}>
      <MyComponent />
    </ErrorBoundary>
  );
}

ریست کردن Error Boundryها

یکی از قوی‌ترین ویژگی‌های کتابخانه react-error-boundary، امکان ریست کردن state مربوط به error boundary است که به معنای پاک کردن خطا و تلاش برای رندر کردن درخت کامپوننت می‌باشد. هنگامی که یک خطا ممکن است گذرا باشد، مانند یک خطای شبکه که به دلیل قطع موقت رخ می‌دهد، استفاده از این ویژگی می‌تواند بسیار مفید باشد.

Error Boundary را می‌توانیم با استفاده از تابع resetErrorBoundary ارائه شده به کامپوننت fallback ریست کنیم. به عنوان مثال، می‌توانیم این تابع را در پاسخ به کلیک یک دکمه فراخوانی کنیم و به کاربران اجازه دهیم تا به صورت دستی یک عملیات ناموفق را دوباره امتحان کنند.

ErrorBoundary همچنین یک تابع onReset را دریافت می‌کند، تابعی که درست قبل از تنظیم مجدد state خطا، فراخوانی می‌شود. این تابع برای انجام هرگونه پاکسازی یا ریست state در برنامه که باید قبل از رندر مجدد پس از یک خطا اتفاق بیفتد، مفید می‌باشد.

در نهایت، prop resetKeys آرایه‌ای از مقادیر است که با تغییر آن، ریست کردن error boundary را آغاز می‌کند. این کار زمانی می‌تواند مفید باشد که می‌دانیم با تغییر دادن مقادیر خاص یا مقادیر state، خطا باید برطرف شود. در ادامه مثالی از نحوه استفاده از این propها را بررسی می‌کنیم:

import { ErrorBoundary } from 'react-error-boundary'

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  )
}

function MyComponent({ someKey }) {
  // Some component logic that may throw JS errors
}

function App() {
  const [someKey, setSomeKey] = React.useState(null)

  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      onReset={() => setSomeKey(null)} // reset the state of your app here
      resetKeys={[someKey]} // reset the error boundary when `someKey` changes
    >
      <MyComponent someKey={someKey} />
    </ErrorBoundary>
  )
}

در این مثال، اگر خطایی در MyComponent مشاهده شود، کامپوننت ErrorFallback و پیام خطا و دکمه Try again به کاربران نمایش داده می‌شود. کاربر با کلیک بر روی این دکمه، resetErrorBoundary را فراخوانی می‌کند که تابع onReset را راه‌اندازی کرده و state خطا را پاک می‌کند و در نتیجه MyComponent دوباره رندر می‌شود. اگر prop someKey تغییر کند، error boundary نیز ریست می‌شود و روشی انعطاف‌پذیر برای بازیابی خطاها بر اساس تغییراتی که در state برنامه صورت می‌گیرد، ارائه می‌کند.

بررسی هوک useErrorBoundary در کتابخانه react-error-boundary

هوک useErrorBoundary یکی دیگر از ویژگی‌های مفیدی است که توسط react-error-boundary ارائه می‌شود. این یک هوک سفارشی است که به ما این امکان را می‌دهد تا error boundaryها را به راحتی نشان داده و رد کنیم.

استفاده از هوک useErrorBoundary به ویژه هنگام کار با کدهای asynchronous می‌تواند مفید باشد. در ادامه مثالی از نحوه استفاده از هوک useErrorBoundary را بررسی می‌کنیم:

import { useErrorBoundary } from 'react-error-boundary'

function MyComponent() {
  const { showBoundary } = useErrorBoundary();

  async function fetchData() {
    try {
      // fetch some data
    } catch (error) {
      showBoundary(error);
    }
  }

  return (
    ...
  );
}

function App() {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <MyComponent />
    </ErrorBoundary>
  );
}

در این مثال، MyComponent از useErrorBoundary برای دریافت تابعی استفاده می‌کند که می‌تواند همراه با خطا فراخوانی شود. تابع fetchData یک تابع async است که برخی از داده‌ها را دریافت می‌کند و هر گونه خطا را تشخیص می‌دهد. اگر خطایی رخ دهد، به تابع handleError منتقل می‌شود، که خطا را throw می‌کند تا بتوانیم آن را توسط ErrorBoundary بگیریم.

useErrorBoundary یک راه قدرتمند برای رسیدگی به خطاها در کامپوننت‌های فانکشنال ارائه می‌دهد. این هوک به صورت یکپارچه با کامپوننت ErrorBoundary از کتابخانه react-error-boundary کار می‌کند و مدیریت خطا در React را به فرآیندی بسیار ساده‌تر تبدیل می‌نماید.

همچنین می‌توانیم با استفاده از متدی که توسط هوک useErrorBoundary ارائه می‌شود، error boundary را ریست کنیم. resetBoundary error boundary را برای امتحان مجدد رندری که در ابتدا ناموفق بود، درخواست می‌کند:

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

function ErrorFallback({ error }) {
  const { resetBoundary } = useErrorBoundary();

  return (
    <div>
      <p>Something went wrong:</p>
      <pre>{error.message}</pre>
      <button onClick={resetBoundary}>Try again</button>
    </div>
  );
}

بررسی تابع withErrorBoundary به عنوان HOC

در حالی که هوک‌های React و کامپوننت‌های فانکشنال به طور فزاینده‌ای محبوب شده و مورد توجه توسعه‌دهندگان قرار گرفته‌اند، اما هنوز موارد زیادی وجود دارد که ممکن است بخواهیم با استفاده از کلاس کامپوننت‌ها کار کنیم یا این که الگوی higher-order component (HOC) را ترجیح دهیم. پکیج react-error-boundary نیز با استفاده از withErrorBoundary HOC راه‌حلی برای این موضوع ارائه می‌دهد.

withErrorBoundary یک higher-order component است که یک کامپوننت معین را داخل یک error boundary قرار می‌دهد. این کار می‌تواند یک روش مفید برای افزودن error boundaryها به کامپوننت‌های خود، بدون تغییر پیاده‌سازی آن‌ها و یا افزودن JSX اضافی به درختان کامپوننتی که داریم باشد. در ادامه نحوه استفاده از withErrorBoundary را باهم بررسی می‌کنیم:

import { withErrorBoundary } from 'react-error-boundary'

function MyComponent() {
  // Your component logic
}

const MyComponentWithErrorBoundary = withErrorBoundary(MyComponent, {
  FallbackComponent: ErrorFallback,
  onError: logErrorToService,
  onReset: handleReset,
  resetKeys: ['someKey']
});

function App() {
  return <MyComponentWithErrorBoundary someKey={someKey} />
}

در این مثال، MyComponent با استفاده از withErrorBoundary داخل یک error boundary قرار گرفته است. آرگومان دوم برای withErrorBoundary یک آبجکت options است، که در آن می‌توانیم همان مواردی را که برای کامپوننت ErrorBoundary ارائه می‌دادیم، تعیین کنیم: FallbackComponent، onError، onReset و resetKeys.

زمانی ما که می‌خواهیم error boundaryها را بدون ایجاد تغییری در پیاده‌سازی آن‌ها به کامپوننت‌های خود اضافه کنیم، یا اگر با یک کلاس کامپوننتی کار می‌کنیم که نمی‌تواند از هوک‌ها استفاده کند، استفاده از این رویکرد HOC می‌تواند یک راه‌حل بسیار مفید باشد. این موضوع، انعطاف‌پذیری react-error-boundary را در تطبیق استایل‌ها و پارادایم‌های مختلف کدگذاری در توسعه React نشان می‌دهد.

مزایای استفاده از react-error-boundary

کتابخانه react-error-boundary طیف وسیعی از مزایا را ارائه می‌دهد که آن را به یک راه‌حل ایده‌آل برای مدیریت خطا در برنامه‌های React تبدیل می‌کند. در ادامه چند مورد از این مزایای کلیدی را مشاهده می‌کنیم:

سادگی

کتابخانه react-error-boundary یک API ساده و شهودی را ارائه می‌کند که درک و استفاده از آن آسان است. این کتابخانه، پیچیدگی‌های مدیریت خطا را از بین می‌برد و روشی ساده برای انجام آن به توسعه‌دهندگان ارائه می‌دهد.

سازگاری بالا با کامپوننت فانکشنال

بر خلاف error boundaryهای سنتی در React، که نیاز به استفاده از کلاس کامپوننت‌ها دارند، react-error-boundary با در نظر گرفتن کامپوننت‌های فانکشنال ساخته شده است. این کتابخانه از هوک‌ها استفاده می‌کند که با روندهای فعلی در توسعه React هماهنگی بیشتری دارد.

تطبیق‌پذیری

کتابخانه react-error-boundary راه‌های متعددی را برای استفاده از error boundaryها ارائه می‌دهد، از جمله به عنوان یک کامپوننت، با HOC، یا از طریق یک هوک سفارشی. این تطبیق‌پذیری به توسعه‌دهندگان این امکان را می‌دهد تا بهترین رویکرد را برای نیازها و استایل کدنویسی خود انتخاب کنند.

fallback UI با قابلیت تنظیم

react-error-boundary اجازه می‌دهد تا در صورت بروز خطا، یک fallback UI قابل تنظیم نمایش داده شود. این کار می‌تواند UX بسیار بهتری را نسبت به خراب شدن برنامه یا نمایش صفحه خالی ارائه دهد.

فانکشنالیتی Reset

این کتابخانه می‌تواند state خطا را ریست کند و به برنامه اجازه دهد تا از خطاها بازیابی انجام دهد. این ویژگی به ویژه برای خطاهای گذرا که بدون لود مجدد کامل صفحه قابل حل هستند، بسیار مفید می‌باشد.

گزارش خطا

این کتابخانه با استفاده از prop onError، می‌تواند خطاها را در یک سرویس گزارش خطا ثبت کرده و اطلاعات ارزشمندی را برای دیباگ کردن و حل مشکلات ارائه نماید.

Community و نگه‌داری از آن

react-error-boundary به طور فعال نگه‌داری می‌شود و به شکل گسترده در جامعه React مورد استفاده قرار می‌گیرد. بنابراین می‌توانیم منتظر به روز رسانی‌ها و بهبودهای منظم باشیم.

شناسایی همه خطاها و مکانیسم‌های retry

یک نکته مهم هنگام اجرای error boundaryها، اطمینان از این است که تمام خطاهای احتمالی در برنامه ما به درستی شناسایی و رسیدگی می‌شوند. کتابخانه react-error-boundary به این امر کمک کرده و توانایی کشف خطاها را از هر نقطه در درخت کامپوننت فراهم می‌کند.

این موضوع در مورد اینکه آیا خطاها از متدهای lifecycle یک کلاس کامپوننت، تابع رندر یک کامپوننت فانکشنال یا حتی کد asynchronous هنگام استفاده از هوک useErrorHandler شناسایی شده‌اند، صدق می‌کند. با این حال، تشخیص خطاها اولین قدم بوده و به اندازه این که می‌خواهیم تصمیم بگیریم پس از شناسایی خطا چه کاری با آن انجام دهیم، مهم می‌باشد. اینجاست که مفهوم مکانیسم‌های retry مطرح می‌شود.

مکانیسم retry راهی است که اپلیکیشن ما از آن برای بازیابی خطا استفاده می‌کند، که این کار را اغلب با تلاش مجدد عملیات ناموفق انجام می‌دهد.

react-error-boundary از طریق تابع resetErrorBoundary و prop resetKeys از مکانیزم‌های retry پشتیبانی می‌کند. می‌توانیم resetErrorBoundary را برای پاک کردن خطا و رندر مجدد درخت کامپوننت فراخوانی کنیم. این کار می‌تواند به صورت دستی فعال شود، مثلاً در پاسخ به کلیک دکمه، به کاربران اجازه می‌دهد تا یک عملیات ناموفق را دوباره امتحان کنند.

resetKeys آرایه‌ای از مقادیر است که با تغییر آن، ریست کردن error boundary را آغاز می‌کند. این ویژگی به error boundary اجازه می‌دهد تا به‌طور خودکار retry کند، و هنگامی که مقادیر خاص یا مقادیر state تغییر می‌کنند درخت کامپوننت را رندر نماید. در ادامه مثالی از نحوه پیاده‌سازی مکانیزم retry با استفاده از react-error-boundary را داریم:

import { ErrorBoundary } from 'react-error-boundary'

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  )
}

function MyComponent({ retryCount }) {
  // Some component logic that may throw JS errors
}

function App() {
  const [retryCount, setRetryCount] = React.useState(0)

  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      onReset={() => setRetryCount(retryCount + 1)} // increment the retry count on reset
      resetKeys={[retryCount]} // reset the error boundary when `retryCount` changes
    >
      <MyComponent retryCount={retryCount} />
    </ErrorBoundary>
  )
}

در این مثال، کامپوننت App state retryCount را حفظ می‌کند. هنگامی که بر روی دکمه "Try again" در کامپوننت ErrorFallback کلیک می‌کنیم، resetErrorBoundary را فراخوانی می‌کند. سپس onReset را فعال کرده و پس از آن state خطا را پاک می‌کند.

onReset مقدار retryCount را افزایش می‌دهد، که پس از آن به دلیل تغییر در resetKeys، error boundary ریست می‌شود و در نتیجه MyComponent دوباره رندر می‌گردد.

الگوهای طراحی رایج برای پیاده‌سازی خطا

Boundaryها

هنگام پیاده‌سازی error boundaryها در برنامه‌های React می‌توانیم از چندین الگوی طراحی استفاده کنیم. بهترین مورد برای استفاده، به برنامه‌ای که داریم و معماری آن بستگی دارد.

Error Boundaryهای Component-level

این رویکرد شامل قرار دادن کامپپوننت‌های جداگانه در error boundaryها می‌باشد که سطح بالایی از جزئیات را فراهم می‌کند و به ما این امکان را می‌دهد تا خطاهای هر کامپوننت را به صورت جداگانه مدیریت کنیم.

اگر یک کامپوننت دچار مشکل شده باشد، error boundary می‌تواند خطا را بگیرد و از انتشار آن در درخت کامپوننت جلوگیری نماید. این بدان معنی است که فقط کامپوننت مشکل‌دار تحت تأثیر قرار می‌گیرد و بقیه برنامه می‌تواند به طور عادی به کار خود ادامه دهد.

استفاده از error boundaryهای Component-level مخصوصاً زمانی مفید است که ما کامپوننت‌هایی در برنامه خود داریم که از یکدیگر جدا شده‌اند و state مشترکی ندارند. اگر یکی از کامپوننت‌ها از کار بیفتد، تاثیری بر روی سایر کامپوننت‌ها نمی‌گذارد. با این حال، اگر بسیاری از کامپوننت‌ها به error boundaryهای خاص خود نیاز داشته باشند، این رویکرد می‌تواند منجر به ایجاد تکرارهای زیادی شود.

Error Boundaryهای Layout-level

Error Boundaryهای Layout-level در درخت کامپوننت در سطح بالاتر قرار دارند و اغلب گروه‌هایی از کامپوننت‌های مرتبط درون آن قرار می‌گیرند. این گزینه زمانی که ما کامپوننت‌های نزدیک به هم داشته باشیم که state مشترک باهم دارند، انتخاب خوبی به شمار می‌آید.

وقتی خطایی در یک کامپوننت رخ می‌دهد، error boundary Layout-level می‌تواند آن را بگیرد و یک پیام خطا یا یک fallback UI برای کل گروه کامپوننت‌ها نمایش دهد. این روش می‌تواند راه خوبی برای رسیدگی به خطاهایی باشد که بر کل بخش برنامه ما تأثیر می‌گذارد، مانند نوار sidebar یا داشبورد.

در این روش اگر در یکی از کامپوننت‌ها خطایی ایجاد شود می‌تواند کل گروه کامپوننت‌ها را تحت تأثیر قرار دهد، حتی اگر سایر کامپوننت‌ها به درستی کار کنند.

Error Boundaryهای Top-level

error boundaryهای Top-level در بالاترین قسمت درخت کامپوننت قرار می‌گیرند. آن‌ها یک راه حل جامع هستند که می‌توانند هر خطایی که در برنامه ما رخ می‌دهد را کنترل کنند. این رویکرد تضمین می‌کند که اگر خطایی در هر جایی از برنامه رخ دهد، می‌توانیم آن را به‌خوبی شناسایی و مدیریت کنیم.

این رویکرد می‌تواند از خراب شدن کل برنامه ما در صورت بروز خطا جلوگیری کند. با این حال، یک خطا می‌تواند بر روی کل برنامه تأثیر بگذارد، نه فقط کامپوننت یا گروهی از کامپوننت‌هایی که در آن‌ها خطا رخ داده است.

مدیریت خطاهای async

همانطور که در بخش‌های قبلی مقاله دیدیم، react-error-boundary خطاهای async را شناسایی نمی‌کند. این به این دلیل است که کدهای async خارج از رندر اجرا می‌شوند. هنگامی که خطایی در یک promise یا یک تابع async رخ می‌دهد، یک promise ریجکت شده را return می‌کند. کتابخانه react-error-boundary برای شناسایی خطاهای synchronousای که در طول متدهای rendering و lifecycle رخ می‌دهند طراحی شده است. یعنی خطاهایی را که در مرحله رندر رخ می‌دهند کنترل می‌کند، بنابراین خطاهای async را نمی‌گیرد.

به طور مشابه، توابعی مانند setTimeout و RequestAnimationFrame برنامه‌ریزی می‌کنند تا کدها را پس از تکمیل رندر اجرا کنند، به این معنی که آن‌ها خارج از پشته اجرا، اجرا می‌شوند و بنابراین گرفتار react-error-boundary نمی‌شوند.

بنابراین، هنگام وقوع چنین خطاهایی چگونه باید با آن‌ها برخورد کنیم؟ برای رسیدگی به خطاهای async که react-error-boundary نمی‌تواند آن‌ها شناسایی کند، می‌توانیم یک هوک سفارشی ایجاد کنیم که توسط ErrorBoundary در بلاک catch async ما فعال می‌شود. به عنوان مثال:

import React, { useState, useEffect, useCallback } from "react";
import { ErrorBoundary } from "react-error-boundary";

const useAsyncError = () => {
  const [_, setError] = useState();
  return useCallback(
    (error) => {
      setError(() => {
        throw error;
      });
    },
    [setError]
  );
};

const BrokenComponent = () => {
  const [data, setData] = useState(null);
  const throwError = useAsyncError();

  useEffect(() => {
    fetch("http://some-site.wtf/rest")
      .then((res) => res.json())
      .then(setData)
      .catch((e) => {
        throwError(e);
      });
  }, []);
  return data;
};

export default function App() {
  return (
    <ErrorBoundary fallback={<p>Something went wrong</p>}>
      <BrokenComponent />
    </ErrorBoundary>
  );
}

هوک سفارشی useAsyncError هنگامی که خطای async رخ می‌دهد فراخوانی شده و سپس ErrorBoundary را trigger می‌کند. همچنین می‌توانیم پیام‌های خاصی را در بلاک catch تعریف کنیم:

.catch((e) => {
    throwError(new Error("Async Error"));
});

از طرف دیگر، react-error-boundary اکنون یک هوک به نام useErrorBoundary را شامل می‌شود که می‌توانیم از آن برای تشخیص خطاهای async استفاده کنیم:

import React, { useState, useEffect } from "react";
import { ErrorBoundary, useErrorBoundary } from "react-error-boundary";

const BrokenComponent = () => {
  const [data, setData] = useState(null);
  const { showBoundary } = useErrorBoundary();

  useEffect(() => {
    fetch("http://some-site.wtf/rest")
      .then((res) => res.json())
      .then(setData)
      .catch((e) => {
        showBoundary(e);
      });
  }, []);
  return data;
};

export default function App() {
  return (
    <ErrorBoundary fallback={<p>Something went wrong</p>}>
      <BrokenComponent />
    </ErrorBoundary>
  );
}

تست Error Boundaryها در React

تست برای توسعه نرم‌افزار یک امر ضروری است و error boundaryهای React نیز از این قاعده مستثنی نیستند. آزمایش صحیح error boundaryها تضمین می‌کند که آن‌ها به درستی عمل می‌کنند و خطاها را همانطور که انتظار می‌رود مدیریت می‌کنند. می‌توانیم از ابزارهای آزمایشی مانند Jest و React Testing Library برای نوشتن تست‌های واحد برای error boundaryهای خود استفاده نماییم.

این تست‌ها می‌توانند خطاها را در یک کامپوننت شبیه‌سازی کنند و تأیید نمایند که error boundaryخطا را می‌گیرد و fallback UI را به درستی ارائه می‌دهد. به عنوان مثال:

import { render } from "@testing-library/react";
import ErrorBoundary from "../ErrorBoundary";
import ProblematicComponent from "../ProblematicComponent";

it("catches error and renders message", () => {
  console.error = jest.fn();

  render(
    <ErrorBoundary>
      <ProblematicComponent />
    </ErrorBoundary>
  );

  expect(screen.getByText("Something went wrong.")).toBeInTheDocument();
});

در این تست، <ProblematicComponent /> به گونه‌ای طراحی شده است که عمداً خطا ایجاد کند، و کامپوننت ErrorBoundary باید خطا را بگیرد و متن Something went wrong را رندر کند.

جمع‌بندی

ما در این مقاله سعی کردیم مدیریت خطا در React با استفاده از کتابخانه react-error-boundary را بررسی کنیم. این کتابخانه هم کلاس کامپوننت‌ها و هم کامپوننت‌های فانکشنال را پوشش می‌دهد. API منعطف آن، شامل کامپوننت‌ها، کامپوننت‌های higher-order و هوک‌های سفارشی، راه‌های مختلفی را برای ادغام مدیریت خطا در کامپوننت‌های ما فراهم می‌کند. علاوه بر این، پشتیبانی آن از fallback UIهای سفارشی، فانکشنالیتی ریست کردن خطا و گزارش‌دهی خطا به ما کمک می‌کند که یک UX قدرتمند داشته باشیم.

قرار دادن کتابخانه react-error-boundary در برنامه React می‌تواند منجر به مدیریت بهتر خطا، دیباگ کردن آسان‌تر و در نتیجه، محصول نهایی بهتر شود.