diff --git a/demo/DemoApp.tsx b/demo/DemoApp.tsx
index 53842a5..0cad5db 100644
--- a/demo/DemoApp.tsx
+++ b/demo/DemoApp.tsx
@@ -36,6 +36,8 @@ import {useNotify} from "../src/Components/Notifications/Notifications";
import {Notification, NotificationType} from "../src/Components/Notifications/Notification";
import {Box} from "../src/Components/Box";
import {Await, useAsync} from "../src/Async";
+import {NotifyErrorsBoundary} from "../src/Components/Errors/NotifyErrorsBoundary";
+import {DemoFailingComponent, DemoResetComponent} from "./DemoFailingComponent";
export function DemoApp()
{
@@ -68,6 +70,8 @@ export function DemoApp()
}, 2000);
}), [asyncChange]);
+ const [failingComponentsCount, setFailingComponentsCount] = useState(0);
+
return (
@@ -584,8 +588,20 @@ export function DemoApp()
Data: {data}
)}
-
-
+
+
+
+
+ Error boundaries
+
+
+
+ }>
+ {
+ [...Array(failingComponentsCount)].map(() => )
+ }
+
diff --git a/demo/DemoFailingComponent.tsx b/demo/DemoFailingComponent.tsx
new file mode 100644
index 0000000..e2f3231
--- /dev/null
+++ b/demo/DemoFailingComponent.tsx
@@ -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 (
I will never be shown...
);
+}
+
+/**
+ * A simple demo reset component.
+ */
+export function DemoResetComponent()
+{
+ // Get error boundary.
+ const errorBoundary = useErrorBoundary();
+
+ return (
+
+ );
+}
diff --git a/index.ts b/index.ts
index 3ed05de..04e8e5e 100644
--- a/index.ts
+++ b/index.ts
@@ -12,6 +12,7 @@ export {useCurtain} from "./src/Components/Curtains/CurtainInstance";
export type {CurtainContextState} from "./src/Components/Curtains/CurtainInstance";
export * from "./src/Components/Dates/Calendar";
export * from "./src/Components/Dates/Datepicker";
+export * from "./src/Components/Errors/NotifyErrorsBoundary";
export * from "./src/Components/Floating/Float";
export * from "./src/Components/Floating/Tooltip";
export * from "./src/Components/Forms/Checkbox";
diff --git a/package.json b/package.json
index d935e29..9683d08 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{
- "version": "1.5.0",
+ "version": "1.6.0",
"name": "@kernelui/core",
"description": "Kernel UI Core.",
"scripts": {
@@ -21,6 +21,7 @@
"@fontsource-variable/jetbrains-mono": "^5.0.21",
"@fontsource-variable/manrope": "^5.0.20",
"@fontsource-variable/source-serif-4": "^5.0.19",
+ "react-error-boundary": "^4.0.13",
"react-merge-refs": "^2.1.1",
"uuid": "^10.0.0"
},
diff --git a/src/Components/Errors/NotifyErrorsBoundary.tsx b/src/Components/Errors/NotifyErrorsBoundary.tsx
new file mode 100644
index 0000000..2bd22e0
--- /dev/null
+++ b/src/Components/Errors/NotifyErrorsBoundary.tsx
@@ -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>)
+{
+ // Get notification function.
+ const notify = useNotify();
+
+ /**
+ * Error handling function.
+ */
+ const handleError = useCallback((error: Error, info: React.ErrorInfo) => {
+ // Show a notification about the error.
+ notify(Unexpected error: {error.message});
+ // 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 (
+
+ {children}
+
+ );
+}
diff --git a/yarn.lock b/yarn.lock
index ee5391d..baa1c45 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -229,6 +229,15 @@ __metadata:
languageName: node
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":
version: 7.24.6
resolution: "@babel/template@npm:7.24.6"
@@ -576,6 +585,7 @@ __metadata:
less: "npm:^4.2.0"
react: "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-router-dom: "npm:^6.24.1"
typescript: "npm:^5.4.5"
@@ -2331,6 +2341,17 @@ __metadata:
languageName: node
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":
version: 2.1.1
resolution: "react-merge-refs@npm:2.1.1"
@@ -2378,6 +2399,13 @@ __metadata:
languageName: node
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":
version: 1.19.0
resolution: "resolve@npm:1.19.0"