Replace react-error-boundary with own implementation (#26094)

Closes #21515

Signed-off-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
Jon Koops 2024-01-10 19:37:59 +01:00 committed by GitHub
parent 0f48027ffb
commit 326ba672cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 112 additions and 56 deletions

View file

@ -34,6 +34,3 @@ updates:
labels: labels:
- area/dependencies - area/dependencies
- team/ui - team/ui
ignore:
- dependency-name: react-error-boundary
update-types: ["version-update:semver-major"]

View file

@ -81,7 +81,6 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",
"react-error-boundary": "^3.1.4",
"react-hook-form": "^7.49.3", "react-hook-form": "^7.49.3",
"react-i18next": "^14.0.0", "react-i18next": "^14.0.0",
"react-router-dom": "^6.21.1", "react-router-dom": "^6.21.1",

View file

@ -1,6 +1,5 @@
import { Page } from "@patternfly/react-core"; import { Page } from "@patternfly/react-core";
import { PropsWithChildren, Suspense } from "react"; import { PropsWithChildren, Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
import { Help, mainPageContentId } from "ui-shared"; import { Help, mainPageContentId } from "ui-shared";
@ -10,6 +9,10 @@ import { AlertProvider } from "./components/alert/Alerts";
import { PageBreadCrumbs } from "./components/bread-crumb/PageBreadCrumbs"; import { PageBreadCrumbs } from "./components/bread-crumb/PageBreadCrumbs";
import { ErrorRenderer } from "./components/error/ErrorRenderer"; import { ErrorRenderer } from "./components/error/ErrorRenderer";
import { KeycloakSpinner } from "./components/keycloak-spinner/KeycloakSpinner"; import { KeycloakSpinner } from "./components/keycloak-spinner/KeycloakSpinner";
import {
ErrorBoundaryFallback,
ErrorBoundaryProvider,
} from "./context/ErrorBoundary";
import { RealmsProvider } from "./context/RealmsContext"; import { RealmsProvider } from "./context/RealmsContext";
import { RecentRealmsProvider } from "./context/RecentRealms"; import { RecentRealmsProvider } from "./context/RecentRealms";
import { AccessContextProvider } from "./context/access/Access"; import { AccessContextProvider } from "./context/access/Access";
@ -20,21 +23,23 @@ import { SubGroups } from "./groups/SubGroupsContext";
import { AuthWall } from "./root/AuthWall"; import { AuthWall } from "./root/AuthWall";
const AppContexts = ({ children }: PropsWithChildren) => ( const AppContexts = ({ children }: PropsWithChildren) => (
<RealmsProvider> <ErrorBoundaryProvider>
<RealmContextProvider> <RealmsProvider>
<WhoAmIContextProvider> <RealmContextProvider>
<RecentRealmsProvider> <WhoAmIContextProvider>
<AccessContextProvider> <RecentRealmsProvider>
<Help> <AccessContextProvider>
<AlertProvider> <Help>
<SubGroups>{children}</SubGroups> <AlertProvider>
</AlertProvider> <SubGroups>{children}</SubGroups>
</Help> </AlertProvider>
</AccessContextProvider> </Help>
</RecentRealmsProvider> </AccessContextProvider>
</WhoAmIContextProvider> </RecentRealmsProvider>
</RealmContextProvider> </WhoAmIContextProvider>
</RealmsProvider> </RealmContextProvider>
</RealmsProvider>
</ErrorBoundaryProvider>
); );
export const App = () => { export const App = () => {
@ -47,13 +52,7 @@ export const App = () => {
breadcrumb={<PageBreadCrumbs />} breadcrumb={<PageBreadCrumbs />}
mainContainerId={mainPageContentId} mainContainerId={mainPageContentId}
> >
<ErrorBoundary <ErrorBoundaryFallback fallback={ErrorRenderer}>
FallbackComponent={ErrorRenderer}
onReset={() =>
(window.location.href =
window.location.origin + window.location.pathname)
}
>
<ServerInfoProvider> <ServerInfoProvider>
<Suspense fallback={<KeycloakSpinner />}> <Suspense fallback={<KeycloakSpinner />}>
<AuthWall> <AuthWall>
@ -61,7 +60,7 @@ export const App = () => {
</AuthWall> </AuthWall>
</Suspense> </Suspense>
</ServerInfoProvider> </ServerInfoProvider>
</ErrorBoundary> </ErrorBoundaryFallback>
</Page> </Page>
</AppContexts> </AppContexts>
); );

View file

@ -5,12 +5,17 @@ import {
AlertVariant, AlertVariant,
PageSection, PageSection,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import type { FallbackProps } from "react-error-boundary";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
export const ErrorRenderer = ({ error, resetErrorBoundary }: FallbackProps) => { import { type FallbackProps } from "../../context/ErrorBoundary";
export const ErrorRenderer = ({ error }: FallbackProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const reset = () => {
window.location.href = window.location.origin + window.location.pathname;
};
return ( return (
<PageSection> <PageSection>
<Alert <Alert
@ -18,15 +23,10 @@ export const ErrorRenderer = ({ error, resetErrorBoundary }: FallbackProps) => {
variant={AlertVariant.danger} variant={AlertVariant.danger}
title={error.message} title={error.message}
actionClose={ actionClose={
<AlertActionCloseButton <AlertActionCloseButton title={error.message} onClose={reset} />
title={error.message}
onClose={resetErrorBoundary}
/>
} }
actionLinks={ actionLinks={
<AlertActionLink onClick={resetErrorBoundary}> <AlertActionLink onClick={reset}>{t("retry")}</AlertActionLink>
{t("retry")}
</AlertActionLink>
} }
></Alert> ></Alert>
</PageSection> </PageSection>

View file

@ -0,0 +1,76 @@
import {
Component,
type ComponentType,
type FunctionComponent,
type GetDerivedStateFromError,
type ReactNode,
} from "react";
import { createNamedContext, useRequiredContext } from "ui-shared";
export interface ErrorBoundaryContextValue {
error?: Error;
showBoundary: (error: Error) => void;
}
const ErrorBoundaryContext = createNamedContext<
ErrorBoundaryContextValue | undefined
>("ErrorBoundaryContext", undefined);
export const useErrorBoundary = () => useRequiredContext(ErrorBoundaryContext);
export interface ErrorBoundaryProviderProps {
children: ReactNode;
}
export interface ErrorBoundaryProviderState {
error?: Error;
}
export class ErrorBoundaryProvider extends Component<
ErrorBoundaryProviderProps,
ErrorBoundaryProviderState
> {
state: ErrorBoundaryProviderState = {};
static getDerivedStateFromError: GetDerivedStateFromError<
ErrorBoundaryProviderProps,
ErrorBoundaryProviderState
> = (error) => {
return { error };
};
showBoundary = (error: Error) => {
this.setState({ error });
};
render() {
return (
<ErrorBoundaryContext.Provider
value={{ error: this.state.error, showBoundary: this.showBoundary }}
>
{this.props.children}
</ErrorBoundaryContext.Provider>
);
}
}
export interface FallbackProps {
error: Error;
}
export interface ErrorBoundaryFallbackProps {
fallback: ComponentType<FallbackProps>;
children: ReactNode;
}
export const ErrorBoundaryFallback: FunctionComponent<
ErrorBoundaryFallbackProps
> = ({ children, fallback: FallbackComponent }) => {
const { error } = useErrorBoundary();
if (error) {
return <FallbackComponent error={error} />;
}
return children;
};

View file

@ -1,5 +1,5 @@
import { DependencyList, useEffect } from "react"; import { DependencyList, useEffect } from "react";
import { useErrorHandler } from "react-error-boundary"; import { useErrorBoundary } from "../context/ErrorBoundary";
/** /**
* Util function to only set the state when the component is still mounted. * Util function to only set the state when the component is still mounted.
@ -21,12 +21,11 @@ export function useFetch<T>(
callback: (param: T) => void, callback: (param: T) => void,
deps?: DependencyList, deps?: DependencyList,
) { ) {
const onError = useErrorHandler(); const { showBoundary } = useErrorBoundary();
useEffect(() => { useEffect(() => {
const controller = new AbortController(); const controller = new AbortController();
const { signal } = controller; const { signal } = controller;
adminClientCall() adminClientCall()
.then((result) => { .then((result) => {
if (!signal.aborted) { if (!signal.aborted) {
@ -35,7 +34,7 @@ export function useFetch<T>(
}) })
.catch((error) => { .catch((error) => {
if (!signal.aborted) { if (!signal.aborted) {
onError(error); showBoundary(error);
} }
}); });

View file

@ -204,9 +204,6 @@ importers:
react-dropzone: react-dropzone:
specifier: ^14.2.3 specifier: ^14.2.3
version: 14.2.3(react@18.2.0) version: 14.2.3(react@18.2.0)
react-error-boundary:
specifier: ^3.1.4
version: 3.1.4(react@18.2.0)
react-hook-form: react-hook-form:
specifier: ^7.49.3 specifier: ^7.49.3
version: 7.49.3(react@18.2.0) version: 7.49.3(react@18.2.0)
@ -6101,16 +6098,6 @@ packages:
react: 18.2.0 react: 18.2.0
dev: false 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.23.2
react: 18.2.0
dev: false
/react-hook-form@7.49.3(react@18.2.0): /react-hook-form@7.49.3(react@18.2.0):
resolution: {integrity: sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==} resolution: {integrity: sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==}
engines: {node: '>=18', pnpm: '8'} engines: {node: '>=18', pnpm: '8'}
@ -7793,7 +7780,6 @@ packages:
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
react-dropzone: 14.2.3(react@18.2.0) react-dropzone: 14.2.3(react@18.2.0)
react-error-boundary: 3.1.4(react@18.2.0)
react-hook-form: 7.49.3(react@18.2.0) react-hook-form: 7.49.3(react@18.2.0)
react-i18next: 14.0.0(i18next@23.7.16)(react-dom@18.2.0)(react@18.2.0) react-i18next: 14.0.0(i18next@23.7.16)(react-dom@18.2.0)(react@18.2.0)
react-router-dom: 6.21.1(react-dom@18.2.0)(react@18.2.0) react-router-dom: 6.21.1(react-dom@18.2.0)(react@18.2.0)