stacker.news/components/error-boundary.js

81 lines
2.4 KiB
JavaScript
Raw Permalink Normal View History

2023-05-06 21:51:17 +00:00
import { Component } from 'react'
import { StaticLayout } from './layout'
import styles from '@/styles/error.module.css'
2023-07-25 14:14:45 +00:00
import Image from 'react-bootstrap/Image'
import copy from 'clipboard-copy'
import { LoggerContext } from './logger'
import Button from 'react-bootstrap/Button'
import { useToast } from './toast'
2023-02-08 21:56:43 +00:00
2023-05-06 21:51:17 +00:00
class ErrorBoundary extends Component {
2023-02-08 21:56:43 +00:00
constructor (props) {
super(props)
// Define a state variable to track whether is an error or not
this.state = {
hasError: false,
error: undefined,
errorInfo: undefined
}
2023-02-08 21:56:43 +00:00
}
static getDerivedStateFromError (error) {
2023-02-08 21:56:43 +00:00
// Update state so the next render will show the fallback UI
return { hasError: true, error }
}
getErrorDetails () {
let details = this.state.error.stack
if (this.state.errorInfo?.componentStack) {
details += `\n\nComponent stack:${this.state.errorInfo.componentStack}`
}
return details
2023-02-08 21:56:43 +00:00
}
componentDidCatch (error, errorInfo) {
// You can use your own error logging service here
console.log({ error, errorInfo })
this.setState({ errorInfo })
const logger = this.context
logger?.error(this.getErrorDetails())
2023-02-08 21:56:43 +00:00
}
render () {
// Check if the error is thrown
if (this.state.hasError) {
// You can render any custom fallback UI
const errorDetails = this.getErrorDetails()
2023-02-08 21:56:43 +00:00
return (
<StaticLayout footer={false}>
<Image width='500' height='375' className='rounded-1 shadow-sm' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/floating.gif`} fluid />
<h1 className={styles.status} style={{ fontSize: '48px' }}>something went wrong</h1>
{this.state.error && <CopyErrorButton errorDetails={errorDetails} />}
</StaticLayout>
2023-02-08 21:56:43 +00:00
)
}
// Return children components in case of no error
return this.props.children
}
}
ErrorBoundary.contextType = LoggerContext
2023-02-08 21:56:43 +00:00
export default ErrorBoundary
// This button is a functional component so we can use `useToast` hook, which
// can't be easily done in a class component that already consumes a context
const CopyErrorButton = ({ errorDetails }) => {
const toaster = useToast()
const onClick = async () => {
try {
await copy(errorDetails)
toaster?.success?.('copied')
} catch (err) {
console.error(err)
toaster?.danger?.('failed to copy')
}
}
return <Button className='mt-3' onClick={onClick}>copy error information</Button>
}