feat: add error boundary

makes crashes graceful
This commit is contained in:
zyachel 2023-01-22 21:14:46 +05:30
parent 71d1d5b34e
commit 5cc2ef02ce
7 changed files with 104 additions and 28 deletions

View file

@ -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;

View file

@ -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 <Meta title={title} description='you encountered an error page!' />
title={`${message} (${statusCode})`}
description="you encountered an error page!"
/>
<Layout className={styles.error}> <Layout className={styles.error}>
<svg <svg
className={styles.gnu} className={styles.gnu}
focusable="false" focusable='false'
role="img" role='img'
aria-labelledby="gnu-title gnu-desc" aria-labelledby='gnu-title gnu-desc'
> >
<title id="gnu-title">GNU and Tux</title> <title id='gnu-title'>GNU and Tux</title>
<desc id="gnu-desc"> <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> {title}
<span> ({statusCode})</span>
</h1> </h1>
<p className={styles.back}> {misc ? (
Go back to{' '} <>
<Link href="/about"> <p>{misc.subtext}</p>
<a className="link">the homepage</a> <button className={styles.button} onClick={misc.buttonClickHandler}>
</Link> {misc.buttonText}
. </button>
</p> </>
) : (
<p>
Go back to{' '}
<Link href='/about'>
<a className='link'>the homepage</a>
</Link>
.
</p>
)}
</Layout> </Layout>
</> </>
); );

View file

@ -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;

View file

@ -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;

View file

@ -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 <ErrorBoundary>
{...pageProps} <Component
key={key} /* passing key to force react to remound components */ {...pageProps}
/> key={key} /* passing key to force react to remount components */
/>
</ErrorBoundary>
</ThemeProvider> </ThemeProvider>
); );
}; };

View file

@ -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);

View file

@ -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);
}