feat: add error boundary
makes crashes graceful
This commit is contained in:
parent
71d1d5b34e
commit
5cc2ef02ce
7 changed files with 104 additions and 28 deletions
45
src/components/error/ErrorBoundary.tsx
Normal file
45
src/components/error/ErrorBoundary.tsx
Normal 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;
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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,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;
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue