Add error handling component which supports React Router and Kernel applications.
This commit is contained in:
parent
7bde352ea7
commit
4a7e77a8f3
5 changed files with 166 additions and 4 deletions
|
@ -5,6 +5,7 @@ import {createBrowserRouter} from "react-router-dom";
|
|||
import {Kernel} from "../src/Application/Kernel";
|
||||
import {NavTest} from "./NavTest";
|
||||
import {Avocado} from "@phosphor-icons/react";
|
||||
import {ApplicationError} from "../src/Application/ApplicationError";
|
||||
|
||||
// Router initialization.
|
||||
const router = createBrowserRouter([
|
||||
|
@ -17,6 +18,7 @@ const router = createBrowserRouter([
|
|||
element: <NavTest />,
|
||||
}
|
||||
],
|
||||
errorElement: <ApplicationError />,
|
||||
}
|
||||
])
|
||||
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
import React from "react";
|
||||
import {ApplicationError, ApplicationErrorBoundary} from "./ApplicationError";
|
||||
|
||||
/**
|
||||
* Main Kernel UI application.
|
||||
*/
|
||||
export function Application({children}: React.PropsWithChildren<{}>)
|
||||
export function Application({errorElement, children}: React.PropsWithChildren<{
|
||||
errorElement?: React.ReactNode;
|
||||
}>)
|
||||
{
|
||||
return (
|
||||
<ApplicationErrorBoundary errorElement={errorElement ?? <ApplicationError />}>
|
||||
<main className={"app"}>
|
||||
{children}
|
||||
</main>
|
||||
</ApplicationErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
|
94
src/Application/ApplicationError.tsx
Normal file
94
src/Application/ApplicationError.tsx
Normal file
|
@ -0,0 +1,94 @@
|
|||
import React, {useContext, useState} from "react";
|
||||
import {ArrowsClockwise, Bug, BugDroid} from "@phosphor-icons/react";
|
||||
import {useRouteError} from "react-router-dom";
|
||||
|
||||
/**
|
||||
* Application error context.
|
||||
*/
|
||||
const ApplicationErrorContext = React.createContext<Error>(undefined);
|
||||
|
||||
/**
|
||||
* Get current error from context or router.
|
||||
*/
|
||||
export function useApplicationError(): Error
|
||||
{
|
||||
// Get error from context or router.
|
||||
const error = useContext(ApplicationErrorContext);
|
||||
const routeError = useRouteError() as Error;
|
||||
return error ?? routeError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Application error component.
|
||||
*/
|
||||
export function ApplicationError({children}: {
|
||||
children?: (error: Error) => React.ReactElement;
|
||||
})
|
||||
{
|
||||
// Get error from context.
|
||||
const error = useApplicationError();
|
||||
|
||||
// Show details state.
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
|
||||
return (
|
||||
<main className={"error"}>
|
||||
{children ? children(error) : (
|
||||
<>
|
||||
<Bug size={64} weight={"duotone"} />
|
||||
<h1>Error</h1>
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>{error.name}</h2>
|
||||
|
||||
<p>An unexpected error happened and the application was forced to quit.</p>
|
||||
|
||||
<pre className={"error"}>{error.message}</pre>
|
||||
|
||||
<button onClick={() => window.location.reload()}><ArrowsClockwise size={20} /> Restart application</button>
|
||||
|
||||
<div className={"details"}>
|
||||
<button onClick={() => setShowDetails(!showDetails)}><BugDroid size={18} /> {showDetails ? "Hide" : "Show"} details</button>
|
||||
|
||||
{ // Show details if required.
|
||||
showDetails && (
|
||||
<pre>
|
||||
{error.stack}
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error boundary component for the application.
|
||||
*/
|
||||
export class ApplicationErrorBoundary extends React.Component<React.PropsWithChildren<{
|
||||
errorElement: React.ReactNode;
|
||||
}>, {
|
||||
error: Error;
|
||||
}>
|
||||
{
|
||||
static getDerivedStateFromError(error: Error)
|
||||
{
|
||||
return { error: error };
|
||||
}
|
||||
|
||||
render()
|
||||
{
|
||||
if (this.state?.error)
|
||||
// An error happened, showing the application error content.
|
||||
return (
|
||||
<ApplicationErrorContext.Provider value={this.state?.error}>
|
||||
{this.props.errorElement}
|
||||
</ApplicationErrorContext.Provider>
|
||||
);
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
@import "components/_button";
|
||||
@import "components/_card";
|
||||
@import "components/_dates";
|
||||
@import "components/_errors";
|
||||
@import "components/_floating";
|
||||
@import "components/_form";
|
||||
@import "components/_headings";
|
||||
|
|
60
src/styles/components/_errors.less
Normal file
60
src/styles/components/_errors.less
Normal file
|
@ -0,0 +1,60 @@
|
|||
main.error
|
||||
{
|
||||
font-size: 1.2em;
|
||||
|
||||
> svg
|
||||
{
|
||||
display: block;
|
||||
margin: 1em auto;
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
hr
|
||||
{
|
||||
margin: 1em auto;
|
||||
|
||||
width: 20em;
|
||||
max-width: 50%;
|
||||
height: 0.25em;
|
||||
border-radius: 0.1em;
|
||||
|
||||
|
||||
border: none;
|
||||
background: var(--red);
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6
|
||||
{
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p, pre
|
||||
{
|
||||
margin: 1em auto;
|
||||
width: 40em;
|
||||
max-width: 95%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button
|
||||
{
|
||||
display: block;
|
||||
margin: 1em auto;
|
||||
padding: 0.66em 1em;
|
||||
}
|
||||
|
||||
.details
|
||||
{
|
||||
font-size: 0.8em;
|
||||
|
||||
button
|
||||
{}
|
||||
|
||||
pre
|
||||
{
|
||||
width: auto;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue