Replace react-error-boundary
with own implementation (#26094)
Closes #21515 Signed-off-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
parent
0f48027ffb
commit
326ba672cd
7 changed files with 112 additions and 56 deletions
3
.github/dependabot.yml
vendored
3
.github/dependabot.yml
vendored
|
@ -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"]
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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,6 +23,7 @@ import { SubGroups } from "./groups/SubGroupsContext";
|
||||||
import { AuthWall } from "./root/AuthWall";
|
import { AuthWall } from "./root/AuthWall";
|
||||||
|
|
||||||
const AppContexts = ({ children }: PropsWithChildren) => (
|
const AppContexts = ({ children }: PropsWithChildren) => (
|
||||||
|
<ErrorBoundaryProvider>
|
||||||
<RealmsProvider>
|
<RealmsProvider>
|
||||||
<RealmContextProvider>
|
<RealmContextProvider>
|
||||||
<WhoAmIContextProvider>
|
<WhoAmIContextProvider>
|
||||||
|
@ -35,6 +39,7 @@ const AppContexts = ({ children }: PropsWithChildren) => (
|
||||||
</WhoAmIContextProvider>
|
</WhoAmIContextProvider>
|
||||||
</RealmContextProvider>
|
</RealmContextProvider>
|
||||||
</RealmsProvider>
|
</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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
76
js/apps/admin-ui/src/context/ErrorBoundary.tsx
Normal file
76
js/apps/admin-ui/src/context/ErrorBoundary.tsx
Normal 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;
|
||||||
|
};
|
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue