mirror of
https://github.com/spacebarchat/client.git
synced 2024-11-22 02:12:38 +01:00
error boundary and suspense
This commit is contained in:
parent
d5248d2c6b
commit
e9f5994260
@ -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"
|
||||
},
|
||||
|
@ -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'}
|
||||
|
33
src/App.tsx
33
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 (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
|
63
src/components/ErrorBoundary.tsx
Normal file
63
src/components/ErrorBoundary.tsx
Normal 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;
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>,
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user