• בלוג
  • איך להציג שגיאות (ולאפס אותן) בתוך React Router

איך להציג שגיאות (ולאפס אותן) בתוך React Router

29/01/2020

יישומים שמשתמשים ב React Router עשויים להיתקל בשגיאות בעת מעבר לדף מסוים ביישום. באופן רגיל כשריאקט נתקל ב Exception בתוך render התוצאה היא מסך לבן. מסך זה נותן למתכנתים אינדיקציה שמשהו לא בסדר קרה, אבל באותו זמן הוא גם נותן למשתמשים חוויה לא נעימה של תקלה.

מנגנון ה Error Boundaries של ריאקט מאפשר לבנות סוג של בלוקים עבור try ו catch בתוך קומפוננטות: אנחנו מגדירים קומפוננטה שהיא Error Boundary, אותה קומפוננטה תופסת שגיאות שקרו בתוך ה render של הילדים שלה ויכולה להציג ממשק משתמש חלופי עם הודעת שגיאה נחמדה ואפשרות לחזור לעבוד באפליקציה.

ההצעה מהתיעוד של ריאקט למימוש מחלקה 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, errorInfo) {
    // You can also log the error to an error reporting service
    logErrorToMyService(error, errorInfo);
  }

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

    return this.props.children; 
  }
}

ואפשר להשתמש בזה כדי לעטוף קומפוננטה רגילה בצורה הבאה:

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

ביישומים שמשתמשים ב React Router אנחנו רוצים לאפס את השגיאה בצורה אוטומטית כשמשתמש עובר לנתיב אחר, כמו שהיה קורה אם המשתמש היה לוחץ על קישור רגיל לדף אחר. שינוי קטן ב Error Boundary יפתור את הבעיה:

  1. אעטוף את המחלקה ב withRouter כדי לקבל גישה למאפיין location.

  2. אוסיף למחלקה מימוש ל componentDidUpdate שיבדוק אם המיקום השתנה.

  3. במידה והמיקום השתנה אאפס את השגיאה וכך ריאקט ינסה מחדש לרנדר את הילדים.

הקוד אחרי התיקון נראה כך:

const ErrorBoundary = withRouter(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 };
  }

  componentDidUpdate(prevProps, prevState) {
    if (this.props.location !== prevProps.location) {
      this.setState({ hasError: false });
    }
  }

  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    // logErrorToMyService(error, errorInfo);
    console.log(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return (
        <div>
          <h1>Something went wrong.</h1>
          <a href='/'>Back home</a>
        </div>
      );
    }

    return this.props.children;
  }
});