איך להציג שגיאות (ולאפס אותן) בתוך React Router
יישומים שמשתמשים ב 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 יפתור את הבעיה:
אעטוף את המחלקה ב withRouter כדי לקבל גישה למאפיין location.
אוסיף למחלקה מימוש ל componentDidUpdate שיבדוק אם המיקום השתנה.
במידה והמיקום השתנה אאפס את השגיאה וכך ריאקט ינסה מחדש לרנדר את הילדים.
הקוד אחרי התיקון נראה כך:
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;
}
});