diff --git a/package.json b/package.json index 184153f0e..c0161ccb6 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,7 @@ "react-color": "^2.19.3", "react-datepicker": "^4.8.0", "react-dom": "^18.0.0", + "react-error-boundary": "^4.0.11", "react-helmet": "^6.1.0", "react-hot-toast": "^2.4.0", "react-hotkeys": "^1.1.4", diff --git a/src/components/error-boundary.tsx b/src/components/error-boundary.tsx index 225b64023..94ed65d3d 100644 --- a/src/components/error-boundary.tsx +++ b/src/components/error-boundary.tsx @@ -1,10 +1,10 @@ -import React from 'react'; +import React, { ErrorInfo, useRef, useState } from 'react'; +import { ErrorBoundary } from 'react-error-boundary'; import { FormattedMessage } from 'react-intl'; -import { connect } from 'react-redux'; -import { getSoapboxConfig } from 'soapbox/actions/soapbox'; -import * as BuildConfig from 'soapbox/build-config'; +import { NODE_ENV } from 'soapbox/build-config'; import { HStack, Text, Stack } from 'soapbox/components/ui'; +import { useSoapboxConfig } from 'soapbox/hooks'; import { captureSentryException } from 'soapbox/sentry'; import KVStore from 'soapbox/storage/kv-store'; import sourceCode from 'soapbox/utils/code'; @@ -12,72 +12,23 @@ import { unregisterSW } from 'soapbox/utils/sw'; import SiteLogo from './site-logo'; -import type { RootState } from 'soapbox/store'; - -interface Props extends ReturnType { +interface ISiteErrorBoundary { children: React.ReactNode; } -type State = { - hasError: boolean; - error: any; - componentStack: any; - browser?: Bowser.Parser.Parser; -} +/** Application-level error boundary. Fills the whole screen. */ +const SiteErrorBoundary: React.FC = ({ children }) => { + const { links } = useSoapboxConfig(); + const textarea = useRef(null); -class ErrorBoundary extends React.PureComponent { + const [error, setError] = useState(); + const [componentStack, setComponentStack] = useState(); + const [browser, setBrowser] = useState(); - state: State = { - hasError: false, - error: undefined, - componentStack: undefined, - browser: undefined, - }; + const isProduction = NODE_ENV === 'production'; + const errorText = error + componentStack; - textarea: HTMLTextAreaElement | null = null; - - componentDidCatch(error: any, info: any): void { - captureSentryException(error, { - tags: { - // Allow page crashes to be easily searched in Sentry. - ErrorBoundary: 'yes', - }, - }); - - this.setState({ - hasError: true, - error, - componentStack: info && info.componentStack, - }); - - import('bowser') - .then(({ default: Bowser }) => { - this.setState({ - browser: Bowser.getParser(window.navigator.userAgent), - }); - }) - .catch(() => {}); - } - - setTextareaRef: React.RefCallback = c => { - this.textarea = c; - }; - - handleCopy: React.MouseEventHandler = () => { - if (!this.textarea) return; - - this.textarea.select(); - this.textarea.setSelectionRange(0, 99999); - - document.execCommand('copy'); - }; - - getErrorText = (): string => { - const { error, componentStack } = this.state; - return error + componentStack; - }; - - clearCookies: React.MouseEventHandler = (e) => { + const clearCookies: React.MouseEventHandler = (e) => { localStorage.clear(); sessionStorage.clear(); KVStore.clear(); @@ -88,135 +39,142 @@ class ErrorBoundary extends React.PureComponent { } }; - render() { - const { browser, hasError } = this.state; - const { children, links } = this.props; + const handleCopy: React.MouseEventHandler = () => { + if (!textarea.current) return; - if (!hasError) { - return children; - } + textarea.current.select(); + textarea.current.setSelectionRange(0, 99999); - const isProduction = BuildConfig.NODE_ENV === 'production'; + document.execCommand('copy'); + }; - const errorText = this.getErrorText(); + function handleError(error: Error, info: ErrorInfo) { + setError(error); + setComponentStack(info.componentStack); - return ( -
-
-
- - - -
+ captureSentryException(error, { + tags: { + // Allow page crashes to be easily searched in Sentry. + ErrorBoundary: 'yes', + }, + }); -
-
-

- -

-

- - - - ), - }} - /> -

- - - {sourceCode.displayName}: - - {' '}{sourceCode.version} - - - -
- - {!isProduction && ( -
- {errorText && ( -