diff --git a/interface/ErrorFallback.tsx b/interface/ErrorFallback.tsx index c1f5b2ab4..f928eb031 100644 --- a/interface/ErrorFallback.tsx +++ b/interface/ErrorFallback.tsx @@ -1,6 +1,11 @@ import { captureException } from '@sentry/browser'; -import { FallbackProps } from 'react-error-boundary'; -import { useRouteError } from 'react-router'; +import { useEffect, useState } from 'react'; +import { + ErrorBoundary, + ErrorBoundaryPropsWithComponent, + FallbackProps +} from 'react-error-boundary'; +import { Navigate, useRouteError } from 'react-router'; import { useDebugState } from '@sd/client'; import { Button, Dialogs } from '@sd/ui'; @@ -8,18 +13,24 @@ import { showAlertDialog } from './components'; import { useOperatingSystem, useTheme } from './hooks'; import { usePlatform } from './util/Platform'; +const RENDERING_ERROR_LOCAL_STORAGE_KEY = 'was-rendering-error'; + export function RouterErrorBoundary() { const error = useRouteError(); + + const reloadBtn = () => { + location.reload(); + localStorage.setItem(RENDERING_ERROR_LOCAL_STORAGE_KEY, 'true'); + }; + return ( { captureException(error); - location.reload(); - }} - reloadBtn={() => { - location.reload(); + reloadBtn(); }} + reloadBtn={reloadBtn} /> ); } @@ -59,6 +70,23 @@ export function ErrorPage({ const os = useOperatingSystem(); const platform = usePlatform(); const isMacOS = os === 'macOS'; + const [redirecting, _] = useState(() => + localStorage.getItem(RENDERING_ERROR_LOCAL_STORAGE_KEY) + ); + + // If the user is on a page and the user presses "Reset" on the error boundary, it may crash in rendering causing the user to get stuck on the error page. + // If it crashes again, we redirect them instead of infinitely crashing. + useEffect(() => { + if (localStorage.getItem(RENDERING_ERROR_LOCAL_STORAGE_KEY) !== null) { + localStorage.removeItem(RENDERING_ERROR_LOCAL_STORAGE_KEY); + window.location.pathname = '/'; + console.error( + 'Hit error boundary after reloading. Redirecting to overview screen!', + redirecting + ); + } + }); + if (redirecting) return null; // To stop flash of error boundary after `localStorage` is reset in the first render and the check above starts being `false` const resetHandler = () => { showAlertDialog({ @@ -137,3 +165,24 @@ export function ErrorPage({ ); } + +export const BetterErrorBoundary = ({ + children, + FallbackComponent, + ...props +}: ErrorBoundaryPropsWithComponent) => { + useEffect(() => { + const id = setTimeout( + () => localStorage.removeItem(RENDERING_ERROR_LOCAL_STORAGE_KEY), + 1000 + ); + + return () => clearTimeout(id); + }, []); + + return ( + + {children} + + ); +}; diff --git a/interface/index.tsx b/interface/index.tsx index f2fe9bf1b..b5d64bd66 100644 --- a/interface/index.tsx +++ b/interface/index.tsx @@ -8,7 +8,6 @@ import dayjs from 'dayjs'; import advancedFormat from 'dayjs/plugin/advancedFormat'; import duration from 'dayjs/plugin/duration'; import relativeTime from 'dayjs/plugin/relativeTime'; -import { ErrorBoundary } from 'react-error-boundary'; import { RouterProvider, RouterProviderProps } from 'react-router-dom'; import { NotificationContextProvider, @@ -20,7 +19,7 @@ import { TooltipProvider } from '@sd/ui'; import { P2P } from './app/p2p'; import { WithPrismTheme } from './components/TextViewer/prism'; -import ErrorFallback from './ErrorFallback'; +import ErrorFallback, { BetterErrorBoundary } from './ErrorFallback'; export { ErrorPage } from './ErrorFallback'; export * from './app'; @@ -59,7 +58,7 @@ export const SpacedriveInterface = (props: { router: RouterProviderProps['router useLoadBackendFeatureFlags(); return ( - + @@ -70,6 +69,6 @@ export const SpacedriveInterface = (props: { router: RouterProviderProps['router - + ); };