Add error boundaries and a default one with notifications.
This commit is contained in:
parent
a1b0cc816c
commit
cbedfa9d52
6 changed files with 111 additions and 3 deletions
|
@ -36,6 +36,8 @@ import {useNotify} from "../src/Components/Notifications/Notifications";
|
||||||
import {Notification, NotificationType} from "../src/Components/Notifications/Notification";
|
import {Notification, NotificationType} from "../src/Components/Notifications/Notification";
|
||||||
import {Box} from "../src/Components/Box";
|
import {Box} from "../src/Components/Box";
|
||||||
import {Await, useAsync} from "../src/Async";
|
import {Await, useAsync} from "../src/Async";
|
||||||
|
import {NotifyErrorsBoundary} from "../src/Components/Errors/NotifyErrorsBoundary";
|
||||||
|
import {DemoFailingComponent, DemoResetComponent} from "./DemoFailingComponent";
|
||||||
|
|
||||||
export function DemoApp()
|
export function DemoApp()
|
||||||
{
|
{
|
||||||
|
@ -68,6 +70,8 @@ export function DemoApp()
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}), [asyncChange]);
|
}), [asyncChange]);
|
||||||
|
|
||||||
|
const [failingComponentsCount, setFailingComponentsCount] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Application>
|
<Application>
|
||||||
<MainMenu>
|
<MainMenu>
|
||||||
|
@ -584,8 +588,20 @@ export function DemoApp()
|
||||||
<p>Data: {data}</p>
|
<p>Data: {data}</p>
|
||||||
)}
|
)}
|
||||||
</Await>
|
</Await>
|
||||||
<button type={"button"} onClick={() => setAsyncChange(asyncChange + 1)}>Change</button>
|
<button type={"button"} onClick={() => setAsyncChange(asyncChange + 1)}>Change async deps</button>
|
||||||
<button type={"button"} onClick={() => setAnotherChange(anotherChange + 1)}>Another change</button>
|
<button type={"button"} onClick={() => setAnotherChange(anotherChange + 1)}>Change something else</button>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<h2>Error boundaries</h2>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<button type={"button"} className={"error"}
|
||||||
|
onClick={() => setFailingComponentsCount(failingComponentsCount + 1)}>Do something dangerous</button>
|
||||||
|
<NotifyErrorsBoundary fallback={<DemoResetComponent />}>
|
||||||
|
{
|
||||||
|
[...Array(failingComponentsCount)].map(() => <DemoFailingComponent />)
|
||||||
|
}
|
||||||
|
</NotifyErrorsBoundary>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
</Application>
|
</Application>
|
||||||
|
|
26
demo/DemoFailingComponent.tsx
Normal file
26
demo/DemoFailingComponent.tsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import React from "react";
|
||||||
|
import {useErrorBoundary} from "react-error-boundary";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple demo failing component.
|
||||||
|
*/
|
||||||
|
export function DemoFailingComponent()
|
||||||
|
{
|
||||||
|
throw new Error("Proudly thrown error.");
|
||||||
|
return (<p>I will never be shown...</p>);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple demo reset component.
|
||||||
|
*/
|
||||||
|
export function DemoResetComponent()
|
||||||
|
{
|
||||||
|
// Get error boundary.
|
||||||
|
const errorBoundary = useErrorBoundary();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button type={"button"} onClick={() => {
|
||||||
|
errorBoundary.resetBoundary();
|
||||||
|
}}>Reset to try again!</button>
|
||||||
|
);
|
||||||
|
}
|
1
index.ts
1
index.ts
|
@ -12,6 +12,7 @@ export {useCurtain} from "./src/Components/Curtains/CurtainInstance";
|
||||||
export type {CurtainContextState} from "./src/Components/Curtains/CurtainInstance";
|
export type {CurtainContextState} from "./src/Components/Curtains/CurtainInstance";
|
||||||
export * from "./src/Components/Dates/Calendar";
|
export * from "./src/Components/Dates/Calendar";
|
||||||
export * from "./src/Components/Dates/Datepicker";
|
export * from "./src/Components/Dates/Datepicker";
|
||||||
|
export * from "./src/Components/Errors/NotifyErrorsBoundary";
|
||||||
export * from "./src/Components/Floating/Float";
|
export * from "./src/Components/Floating/Float";
|
||||||
export * from "./src/Components/Floating/Tooltip";
|
export * from "./src/Components/Floating/Tooltip";
|
||||||
export * from "./src/Components/Forms/Checkbox";
|
export * from "./src/Components/Forms/Checkbox";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.5.0",
|
"version": "1.6.0",
|
||||||
"name": "@kernelui/core",
|
"name": "@kernelui/core",
|
||||||
"description": "Kernel UI Core.",
|
"description": "Kernel UI Core.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -21,6 +21,7 @@
|
||||||
"@fontsource-variable/jetbrains-mono": "^5.0.21",
|
"@fontsource-variable/jetbrains-mono": "^5.0.21",
|
||||||
"@fontsource-variable/manrope": "^5.0.20",
|
"@fontsource-variable/manrope": "^5.0.20",
|
||||||
"@fontsource-variable/source-serif-4": "^5.0.19",
|
"@fontsource-variable/source-serif-4": "^5.0.19",
|
||||||
|
"react-error-boundary": "^4.0.13",
|
||||||
"react-merge-refs": "^2.1.1",
|
"react-merge-refs": "^2.1.1",
|
||||||
"uuid": "^10.0.0"
|
"uuid": "^10.0.0"
|
||||||
},
|
},
|
||||||
|
|
36
src/Components/Errors/NotifyErrorsBoundary.tsx
Normal file
36
src/Components/Errors/NotifyErrorsBoundary.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import React, {useCallback, useMemo} from "react";
|
||||||
|
import {ErrorBoundary, ErrorBoundaryProps} from "react-error-boundary";
|
||||||
|
import {useNotify} from "../Notifications/Notifications";
|
||||||
|
import {Notification, NotificationType} from "../Notifications/Notification";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A React error boundary that show errors in notifications.
|
||||||
|
*/
|
||||||
|
export function NotifyErrorsBoundary({children, onError, fallback, fallbackRender, FallbackComponent, ...props}: React.PropsWithChildren<Partial<ErrorBoundaryProps>>)
|
||||||
|
{
|
||||||
|
// Get notification function.
|
||||||
|
const notify = useNotify();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error handling function.
|
||||||
|
*/
|
||||||
|
const handleError = useCallback((error: Error, info: React.ErrorInfo) => {
|
||||||
|
// Show a notification about the error.
|
||||||
|
notify(<Notification type={NotificationType.ERROR}>Unexpected error: {error.message}</Notification>);
|
||||||
|
// Then call defined onError, if there is one.
|
||||||
|
onError?.(error, info);
|
||||||
|
}, [onError]);
|
||||||
|
|
||||||
|
// Define default fallback component.
|
||||||
|
const defaultFallback = useMemo(() => <></>, []);
|
||||||
|
|
||||||
|
if (!fallback && !fallbackRender && !FallbackComponent)
|
||||||
|
// Set default fallback if nothing is set.
|
||||||
|
fallback = defaultFallback;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ErrorBoundary onError={handleError} fallback={fallback} {...props}>
|
||||||
|
{children}
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
}
|
28
yarn.lock
28
yarn.lock
|
@ -229,6 +229,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@babel/runtime@npm:^7.12.5":
|
||||||
|
version: 7.25.6
|
||||||
|
resolution: "@babel/runtime@npm:7.25.6"
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime: "npm:^0.14.0"
|
||||||
|
checksum: 10c0/d6143adf5aa1ce79ed374e33fdfd74fa975055a80bc6e479672ab1eadc4e4bfd7484444e17dd063a1d180e051f3ec62b357c7a2b817e7657687b47313158c3d2
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@babel/template@npm:^7.24.6":
|
"@babel/template@npm:^7.24.6":
|
||||||
version: 7.24.6
|
version: 7.24.6
|
||||||
resolution: "@babel/template@npm:7.24.6"
|
resolution: "@babel/template@npm:7.24.6"
|
||||||
|
@ -576,6 +585,7 @@ __metadata:
|
||||||
less: "npm:^4.2.0"
|
less: "npm:^4.2.0"
|
||||||
react: "npm:^18.3.1"
|
react: "npm:^18.3.1"
|
||||||
react-dom: "npm:^18.3.1"
|
react-dom: "npm:^18.3.1"
|
||||||
|
react-error-boundary: "npm:^4.0.13"
|
||||||
react-merge-refs: "npm:^2.1.1"
|
react-merge-refs: "npm:^2.1.1"
|
||||||
react-router-dom: "npm:^6.24.1"
|
react-router-dom: "npm:^6.24.1"
|
||||||
typescript: "npm:^5.4.5"
|
typescript: "npm:^5.4.5"
|
||||||
|
@ -2331,6 +2341,17 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"react-error-boundary@npm:^4.0.13":
|
||||||
|
version: 4.0.13
|
||||||
|
resolution: "react-error-boundary@npm:4.0.13"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": "npm:^7.12.5"
|
||||||
|
peerDependencies:
|
||||||
|
react: ">=16.13.1"
|
||||||
|
checksum: 10c0/6f3e0e4d7669f680ccf49c08c9571519c6e31f04dcfc30a765a7136c7e6fbbbe93423dd5a9fce12107f8166e54133e9dd5c2079a00c7a38201ac811f7a28b8e7
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"react-merge-refs@npm:^2.1.1":
|
"react-merge-refs@npm:^2.1.1":
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
resolution: "react-merge-refs@npm:2.1.1"
|
resolution: "react-merge-refs@npm:2.1.1"
|
||||||
|
@ -2378,6 +2399,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"regenerator-runtime@npm:^0.14.0":
|
||||||
|
version: 0.14.1
|
||||||
|
resolution: "regenerator-runtime@npm:0.14.1"
|
||||||
|
checksum: 10c0/1b16eb2c4bceb1665c89de70dcb64126a22bc8eb958feef3cd68fe11ac6d2a4899b5cd1b80b0774c7c03591dc57d16631a7f69d2daa2ec98100e2f29f7ec4cc4
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"resolve@npm:~1.19.0":
|
"resolve@npm:~1.19.0":
|
||||||
version: 1.19.0
|
version: 1.19.0
|
||||||
resolution: "resolve@npm:1.19.0"
|
resolution: "resolve@npm:1.19.0"
|
||||||
|
|
Loading…
Reference in a new issue