1
0
mirror of https://github.com/spacebarchat/client.git synced 2024-11-25 03:32:54 +01:00

error boundary and suspense

This commit is contained in:
Puyodead1 2023-09-19 15:21:04 -04:00
parent d5248d2c6b
commit e9f5994260
No known key found for this signature in database
GPG Key ID: A4FA4FEC0DD353FC
9 changed files with 127 additions and 42 deletions

View File

@ -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"
},

View File

@ -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'}

View File

@ -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 (
<Loader>
<Routes>
<Route index path="/" element={<AuthenticationGuard component={AppPage} />} />
<Route path="/app" element={<AuthenticationGuard component={AppPage} />} />
<Route
path="/channels/:guildId/:channelId?"
element={<AuthenticationGuard component={ChannelPage} />}
/>
<Route path="/login" element={<UnauthenticatedGuard component={LoginPage} />} />
<Route path="/register" element={<UnauthenticatedGuard component={RegistrationPage} />} />
<Route path="/logout" element={<AuthenticationGuard component={LogoutPage} />} />
<Route path="/swipe" element={<SwipeTest />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</Loader>
<ErrorBoundary section="app">
<Loader>
<Routes>
<Route index path="/" element={<AuthenticationGuard component={AppPage} />} />
<Route path="/app" element={<AuthenticationGuard component={AppPage} />} />
<Route
path="/channels/:guildId/:channelId?"
element={<AuthenticationGuard component={ChannelPage} />}
/>
<Route path="/login" element={<UnauthenticatedGuard component={LoginPage} />} />
<Route path="/register" element={<UnauthenticatedGuard component={RegistrationPage} />} />
<Route path="/logout" element={<AuthenticationGuard component={LogoutPage} />} />
<Route path="/swipe" element={<SwipeTest />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</Loader>
</ErrorBoundary>
);
}

View File

@ -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 (
<Container>
{section === "app" ? (
<>
<h3>App Crash</h3>
<Button onClick={resetError}>Ignore</Button>
<Button onClick={() => location.reload()}>Reload</Button>
</>
) : (
<>
<h3>Component Error</h3>
<Button onClick={resetError}>Ignore</Button>
</>
)}
<br />
<br />
{message}
<br />
<code>{stack}</code>
</Container>
);
}
return <>{children}</>;
}
export default ErrorBoundary;

View File

@ -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 <Component />;
return (
<LoadingSuspense>
<Component />
</LoadingSuspense>
);
};

View File

@ -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 <Component />;
return (
<LoadingSuspense>
<Component />
</LoadingSuspense>
);
};

View File

@ -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(
<BrowserRouter>
<ModalStack renderModals={ModalRenderer}>
<ContextMenuContextProvider>
<BannerContextProvider>
<App />
</BannerContextProvider>
</ContextMenuContextProvider>
<Theme />
</ModalStack>
</BrowserRouter>,
<ErrorBoundaryContext>
<BrowserRouter>
<ModalStack renderModals={ModalRenderer}>
<ContextMenuContextProvider>
<BannerContextProvider>
<App />
</BannerContextProvider>
</ContextMenuContextProvider>
<Theme />
</ModalStack>
</BrowserRouter>
</ErrorBoundaryContext>,
);

View File

@ -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 }) => (
<Suspense fallback={<LoadingPage />}>{children}</Suspense>
);

View File

@ -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 && <ContextMenu {...contextMenu} />}
<GuildSidebar guildId={guildId!} />
<ChannelSidebar channel={channel} guild={guild} channelId={channelId} guildId={guildId} />
<Chat channel={channel} guild={guild} channelId={channelId} guildId={guildId} />
<ErrorBoundary section="component">
<Chat channel={channel} guild={guild} channelId={channelId} guildId={guildId} />
</ErrorBoundary>
</Wrapper>
</Container>
);