Kaynağa Gözat

feat: add error boundary

makes crashes graceful
zyachel 2 yıl önce
ebeveyn
işleme
5cc2ef02ce

+ 45 - 0
src/components/error/ErrorBoundary.tsx

@@ -0,0 +1,45 @@
+import React, { Component, ErrorInfo, ReactNode } from 'react';
+import ErrorInfoComponent from './ErrorInfo';
+
+type Props = {
+  children: ReactNode;
+};
+type State = {
+  hasError: boolean;
+};
+
+class ErrorBoundary extends Component<Props, State> {
+  state: State = {
+    hasError: false,
+  };
+
+  static getDerivedStateFromError(error: Error): State {
+    return { hasError: true };
+  }
+
+  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
+    console.error('Uncaught error:', error, errorInfo);
+  }
+
+  resetError() {
+    this.setState({ hasError: false });
+  }
+
+  render() {
+    if (this.state.hasError)
+      return (
+        <ErrorInfoComponent
+          message='Something weird happened on your browser.'
+          misc={{
+            subtext: 'Check console for more information.',
+            buttonClickHandler: this.resetError.bind(this),
+            buttonText: 'Reload Page',
+          }}
+        />
+      );
+
+    return this.props.children;
+  }
+}
+
+export default ErrorBoundary;

+ 38 - 20
src/components/error/ErrorInfo.tsx

@@ -1,3 +1,4 @@
+import { ReactNode } from 'react';
 import Link from 'next/link';
 import Link from 'next/link';
 import Layout from '../../layouts/Layout';
 import Layout from '../../layouts/Layout';
 import Meta from '../meta/Meta';
 import Meta from '../meta/Meta';
@@ -8,39 +9,56 @@ import styles from '../../styles/modules/components/error/error-info.module.scss
 // description copied verbatim from https://www.gnu.org/graphics/sventsitsky-sadgnu.html
 // description copied verbatim from https://www.gnu.org/graphics/sventsitsky-sadgnu.html
 // 404 idea from ninamori.org 404 page.
 // 404 idea from ninamori.org 404 page.
 
 
-const ErrorInfo = ({ message = 'Not found, sorry.', statusCode = 404 }) => {
+type Props = {
+  message: string;
+  statusCode?: number;
+  // props specific to error boundary.
+  misc?: {
+    subtext: string;
+    buttonText: string;
+    buttonClickHandler: () => void;
+  };
+};
+
+const ErrorInfo = ({ message, statusCode, misc }: Props) => {
+  const title = statusCode ? `${message} (${statusCode})` : message;
   return (
   return (
     <>
     <>
-      <Meta
-        title={`${message} (${statusCode})`}
-        description="you encountered an error page!"
-      />
+      <Meta title={title} description='you encountered an error page!' />
       <Layout className={styles.error}>
       <Layout className={styles.error}>
         <svg
         <svg
           className={styles.gnu}
           className={styles.gnu}
-          focusable="false"
-          role="img"
-          aria-labelledby="gnu-title gnu-desc"
+          focusable='false'
+          role='img'
+          aria-labelledby='gnu-title gnu-desc'
         >
         >
-          <title id="gnu-title">GNU and Tux</title>
-          <desc id="gnu-desc">
+          <title id='gnu-title'>GNU and Tux</title>
+          <desc id='gnu-desc'>
             A pencil drawing of a big gnu and a small penguin, both very sad.
             A pencil drawing of a big gnu and a small penguin, both very sad.
             GNU is despondently sitting on a bench, and Tux stands beside him,
             GNU is despondently sitting on a bench, and Tux stands beside him,
             looking down and patting him on the back.
             looking down and patting him on the back.
           </desc>
           </desc>
-          <use href="/svg/sadgnu.svg#sad-gnu"></use>
+          <use href='/svg/sadgnu.svg#sad-gnu'></use>
         </svg>
         </svg>
         <h1 className={`heading heading__primary ${styles.heading}`}>
         <h1 className={`heading heading__primary ${styles.heading}`}>
-          <span>{message}</span>
-          <span> ({statusCode})</span>
+          {title}
         </h1>
         </h1>
-        <p className={styles.back}>
-          Go back to{' '}
-          <Link href="/about">
-            <a className="link">the homepage</a>
-          </Link>
-          .
-        </p>
+        {misc ? (
+          <>
+            <p>{misc.subtext}</p>
+            <button className={styles.button} onClick={misc.buttonClickHandler}>
+              {misc.buttonText}
+            </button>
+          </>
+        ) : (
+          <p>
+            Go back to{' '}
+            <Link href='/about'>
+              <a className='link'>the homepage</a>
+            </Link>
+            .
+          </p>
+        )}
       </Layout>
       </Layout>
     </>
     </>
   );
   );

+ 1 - 1
src/pages/404.tsx

@@ -1,7 +1,7 @@
 import ErrorInfo from '../components/error/ErrorInfo';
 import ErrorInfo from '../components/error/ErrorInfo';
 
 
 const Error404 = () => {
 const Error404 = () => {
-  return <ErrorInfo />;
+  return <ErrorInfo message='Not found, sorry.' statusCode={404} />;
 };
 };
 
 
 export default Error404;
 export default Error404;

+ 1 - 1
src/pages/500.tsx

@@ -1,6 +1,6 @@
 import ErrorInfo from '../components/error/ErrorInfo';
 import ErrorInfo from '../components/error/ErrorInfo';
 
 
 const Error500 = () => {
 const Error500 = () => {
-  return <ErrorInfo message="server messed up, sorry." statusCode={500} />;
+  return <ErrorInfo message='Server messed up, sorry.' statusCode={500} />;
 };
 };
 export default Error500;
 export default Error500;

+ 7 - 5
src/pages/_app.tsx

@@ -1,10 +1,10 @@
 import type { AppProps } from 'next/app';
 import type { AppProps } from 'next/app';
 import usePageLoading from '../hooks/usePageLoading';
 import usePageLoading from '../hooks/usePageLoading';
 import ProgressBar from '../components/loaders/ProgressBar';
 import ProgressBar from '../components/loaders/ProgressBar';
+import ErrorBoundary from '../components/error/ErrorBoundary';
 import ThemeProvider from '../context/theme-context';
 import ThemeProvider from '../context/theme-context';
 
 
 import '../styles/main.scss';
 import '../styles/main.scss';
-import { useRouter } from 'next/router';
 
 
 const ModifiedApp = ({ Component, pageProps }: AppProps) => {
 const ModifiedApp = ({ Component, pageProps }: AppProps) => {
   const { isPageLoading, key } = usePageLoading();
   const { isPageLoading, key } = usePageLoading();
@@ -12,10 +12,12 @@ const ModifiedApp = ({ Component, pageProps }: AppProps) => {
   return (
   return (
     <ThemeProvider>
     <ThemeProvider>
       {isPageLoading && <ProgressBar />}
       {isPageLoading && <ProgressBar />}
-      <Component
-        {...pageProps}
-        key={key} /* passing key to force react to remound components */
-      />
+      <ErrorBoundary>
+        <Component
+          {...pageProps}
+          key={key} /* passing key to force react to remount components */
+        />
+      </ErrorBoundary>
     </ThemeProvider>
     </ThemeProvider>
   );
   );
 };
 };

+ 1 - 0
src/styles/base/_typography.scss

@@ -7,6 +7,7 @@ body {
   color: var(--clr-text-accent);
   color: var(--clr-text-accent);
   font-family: var(--ff-accent);
   font-family: var(--ff-accent);
   font-weight: var(--fw-medium);
   font-weight: var(--fw-medium);
+  line-height: 1.2;
 
 
   &__primary {
   &__primary {
     font-size: var(--fs-1);
     font-size: var(--fs-1);

+ 11 - 1
src/styles/modules/components/error/error-info.module.scss

@@ -28,6 +28,16 @@
 }
 }
 
 
 .heading {
 .heading {
-  // justify-self: center;
   text-align: center;
   text-align: center;
 }
 }
+
+.button {
+  align-self: end;
+  
+  font: inherit;
+  cursor: pointer;
+  border: none;
+  background: transparent;
+  color: var(--clr-link);
+  border-bottom: 2px solid var(--clr-link);
+}