From e9f5994260364e8387272e588d2b746cb02bfce2 Mon Sep 17 00:00:00 2001 From: Puyodead1 Date: Tue, 19 Sep 2023 15:21:04 -0400 Subject: [PATCH] error boundary and suspense --- package.json | 2 +- pnpm-lock.yaml | 24 ++++--- src/App.tsx | 33 +++++----- src/components/ErrorBoundary.tsx | 63 +++++++++++++++++++ src/components/guards/AuthenticationGuard.tsx | 7 ++- .../guards/UnauthenticatedGuard.tsx | 7 ++- src/index.tsx | 23 ++++--- src/pages/LoadingPage.tsx | 5 ++ src/pages/subpages/ChannelPage.tsx | 5 +- 9 files changed, 127 insertions(+), 42 deletions(-) create mode 100644 src/components/ErrorBoundary.tsx diff --git a/package.json b/package.json index c56514e..8eaea60 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "react-colorful": "^5.6.1", "react-device-detect": "^2.2.3", "react-dom": "^18.2.0", - "react-error-boundary": "^3.1.4", "react-hook-form": "^7.46.1", "react-infinite-scroll-component": "^6.1.0", "react-loading-skeleton": "^3.3.1", @@ -48,6 +47,7 @@ "react-select-search": "^4.1.6", "react-spinners": "^0.13.8", "react-string-replace": "^1.1.1", + "react-use-error-boundary": "^3.0.0", "reoverlay": "^1.0.3", "styled-components": "^5.3.10" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13446bc..ba92b80 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -110,9 +110,6 @@ dependencies: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) - react-error-boundary: - specifier: ^3.1.4 - version: 3.1.4(react@18.2.0) react-hook-form: specifier: ^7.46.1 version: 7.46.1(react@18.2.0) @@ -137,6 +134,9 @@ dependencies: react-string-replace: specifier: ^1.1.1 version: 1.1.1 + react-use-error-boundary: + specifier: ^3.0.0 + version: 3.0.0(react@18.2.0) reoverlay: specifier: ^1.0.3 version: 1.0.3(react-dom@18.2.0)(react@18.2.0) @@ -10524,16 +10524,6 @@ packages: scheduler: 0.23.0 dev: false - /react-error-boundary@3.1.4(react@18.2.0): - resolution: {integrity: sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==} - engines: {node: '>=10', npm: '>=6'} - peerDependencies: - react: '>=16.13.1' - dependencies: - '@babel/runtime': 7.21.5 - react: 18.2.0 - dev: false - /react-error-overlay@6.0.11: resolution: {integrity: sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==} dev: true @@ -10751,6 +10741,14 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /react-use-error-boundary@3.0.0(react@18.2.0): + resolution: {integrity: sha512-5urkfyU3ZzInEMSHe1gxtDzlQAHs0krTt0V6h8H2L5nXhDKq3OYXnCs9lGHDkEkYvLmsphw8ap5g8uYfvrkJng==} + peerDependencies: + react: '>= 16.8' + dependencies: + react: 18.2.0 + dev: false + /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} diff --git a/src/App.tsx b/src/App.tsx index f1b68d4..caea168 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import NotFoundPage from "./pages/NotFound"; import RegistrationPage from "./pages/RegistrationPage"; import { reaction } from "mobx"; +import ErrorBoundary from "./components/ErrorBoundary"; import Loader from "./components/Loader"; import OfflineBanner from "./components/banners/OfflineBanner"; import { UnauthenticatedGuard } from "./components/guards/UnauthenticatedGuard"; @@ -68,21 +69,23 @@ function App() { }, [app.isNetworkConnected, bannerContext]); return ( - - - } /> - } /> - } - /> - } /> - } /> - } /> - } /> - } /> - - + + + + } /> + } /> + } + /> + } /> + } /> + } /> + } /> + } /> + + + ); } diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx new file mode 100644 index 0000000..3943479 --- /dev/null +++ b/src/components/ErrorBoundary.tsx @@ -0,0 +1,63 @@ +// inspired by revite: https://github.com/revoltchat/revite/blob/master/src/lib/ErrorBoundary.tsx + +import React from "react"; +import { useErrorBoundary } from "react-use-error-boundary"; +import styled from "styled-components"; +import Button from "./Button"; + +const Container = styled.div` + height: 100%; + padding: 12px; + background-color: var(--background-secondary); + color: var(--text); + + h3 { + margin: 0; + margin-bottom: 10px; + } + + code { + font-size: 12px; + } +`; + +interface Props { + children: React.ReactNode; + section: "app" | "component"; +} + +function ErrorBoundary({ children, section }: Props) { + const [error, resetError] = useErrorBoundary(); + + // TODO: when v1 is reached, maybe we should add a "report" button here to submit errors to sentry + + if (error) { + const message = error instanceof Error ? error.message : (error as string); + const stack = error instanceof Error ? error.stack : undefined; + return ( + + {section === "app" ? ( + <> +

App Crash

+ + + + ) : ( + <> +

Component Error

+ + + )} +
+
+ {message} +
+ {stack} +
+ ); + } + + return <>{children}; +} + +export default ErrorBoundary; diff --git a/src/components/guards/AuthenticationGuard.tsx b/src/components/guards/AuthenticationGuard.tsx index 6bbe51d..085a4a9 100644 --- a/src/components/guards/AuthenticationGuard.tsx +++ b/src/components/guards/AuthenticationGuard.tsx @@ -1,4 +1,5 @@ import { Navigate } from "react-router-dom"; +import { LoadingSuspense } from "../../pages/LoadingPage"; import { useAppStore } from "../../stores/AppStore"; interface Props { @@ -13,5 +14,9 @@ export const AuthenticationGuard = ({ component }: Props) => { } const Component = component; - return ; + return ( + + + + ); }; diff --git a/src/components/guards/UnauthenticatedGuard.tsx b/src/components/guards/UnauthenticatedGuard.tsx index 5100985..d59230c 100644 --- a/src/components/guards/UnauthenticatedGuard.tsx +++ b/src/components/guards/UnauthenticatedGuard.tsx @@ -1,4 +1,5 @@ import { Navigate } from "react-router-dom"; +import { LoadingSuspense } from "../../pages/LoadingPage"; import { useAppStore } from "../../stores/AppStore"; interface Props { @@ -13,5 +14,9 @@ export const UnauthenticatedGuard = ({ component }: Props) => { } const Component = component; - return ; + return ( + + + + ); }; diff --git a/src/index.tsx b/src/index.tsx index f4b0901..c803a90 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -17,6 +17,7 @@ import calendar from "dayjs/plugin/calendar"; import relativeTime from "dayjs/plugin/relativeTime"; import ReactDOM from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; +import { ErrorBoundaryContext } from "react-use-error-boundary"; import App from "./App"; import ModalRenderer from "./components/modals/ModalRenderer"; import { BannerContextProvider } from "./contexts/BannerContext"; @@ -29,14 +30,16 @@ dayjs.extend(relativeTime); dayjs.extend(calendar, calendarStrings); ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - - - - - - - - - - , + + + + + + + + + + + + , ); diff --git a/src/pages/LoadingPage.tsx b/src/pages/LoadingPage.tsx index 60fd613..0f0bd07 100644 --- a/src/pages/LoadingPage.tsx +++ b/src/pages/LoadingPage.tsx @@ -1,4 +1,5 @@ import { observer } from "mobx-react-lite"; +import { Suspense } from "react"; import PulseLoader from "react-spinners/PulseLoader"; import styled from "styled-components"; import { ReactComponent as SpacebarLogoBlue } from "../assets/images/logo/Logo-Blue.svg"; @@ -50,3 +51,7 @@ function LoadingPage() { } export default observer(LoadingPage); + +export const LoadingSuspense = ({ children }: { children: React.ReactNode }) => ( + }>{children} +); diff --git a/src/pages/subpages/ChannelPage.tsx b/src/pages/subpages/ChannelPage.tsx index 008b72a..e2f81ec 100644 --- a/src/pages/subpages/ChannelPage.tsx +++ b/src/pages/subpages/ChannelPage.tsx @@ -6,6 +6,7 @@ import Banner from "../../components/Banner"; import ChannelSidebar from "../../components/ChannelSidebar"; import ContainerComponent from "../../components/Container"; import ContextMenu from "../../components/ContextMenu"; +import ErrorBoundary from "../../components/ErrorBoundary"; import GuildSidebar from "../../components/GuildSidebar"; import Chat from "../../components/messaging/Chat"; import { BannerContext } from "../../contexts/BannerContext"; @@ -43,7 +44,9 @@ function ChannelPage() { {contextMenu.visible && } - + + + );