Compare commits
4 Commits
52a97f2a97
...
63222f8f28
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63222f8f28 | ||
|
|
9a597e35f3 | ||
|
|
9c78dac495 | ||
|
|
a62036bb5a |
@@ -91,7 +91,8 @@
|
||||
"react": ">= 19.0.0",
|
||||
"react-dom": ">= 19.0.0",
|
||||
"react-hook-form": "^7.71.0",
|
||||
"idb-keyval": "^6.2.2",
|
||||
"use-sync-external-store": ">= 1.4.0",
|
||||
"zustand": ">= 5.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -38,6 +38,9 @@ importers:
|
||||
'@warkypublic/zustandsyncstore':
|
||||
specifier: ^0.0.4
|
||||
version: 0.0.4(react@19.2.4)(use-sync-external-store@1.5.0(react@19.2.4))(zustand@5.0.8(@types/react@19.2.10)(immer@10.1.3)(react@19.2.4)(use-sync-external-store@1.5.0(react@19.2.4)))
|
||||
idb-keyval:
|
||||
specifier: ^6.2.2
|
||||
version: 6.2.2
|
||||
immer:
|
||||
specifier: ^10.1.3
|
||||
version: 10.1.3
|
||||
@@ -2228,6 +2231,9 @@ packages:
|
||||
resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
idb-keyval@6.2.2:
|
||||
resolution: {integrity: sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==}
|
||||
|
||||
ignore@5.3.2:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
@@ -6118,6 +6124,8 @@ snapshots:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
idb-keyval@6.2.2: {}
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
||||
ignore@7.0.5: {}
|
||||
|
||||
70
src/ErrorBoundary/BasicErrorBoundary.tsx
Normal file
70
src/ErrorBoundary/BasicErrorBoundary.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import React, { type PropsWithChildren } from 'react';
|
||||
|
||||
interface ErrorBoundaryProps extends PropsWithChildren {
|
||||
namespace?: string;
|
||||
onReportClick?: () => void;
|
||||
onResetClick?: () => void;
|
||||
onRetryClick?: () => void;
|
||||
reportAPI?: string;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
error: any;
|
||||
errorInfo: any;
|
||||
reported?: boolean;
|
||||
resetted?: boolean;
|
||||
showDetail: boolean;
|
||||
timer?: NodeJS.Timeout | undefined;
|
||||
try: boolean;
|
||||
tryCnt: number;
|
||||
}
|
||||
|
||||
export class ReactBasicErrorBoundary extends React.PureComponent<
|
||||
ErrorBoundaryProps,
|
||||
ErrorBoundaryState
|
||||
> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
error: null,
|
||||
errorInfo: null,
|
||||
showDetail: false,
|
||||
timer: undefined,
|
||||
try: false,
|
||||
tryCnt: 0,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidCatch(error: any, errorInfo: any) {
|
||||
// Catch errors in any components below and re-render with error message
|
||||
this.setState({
|
||||
error,
|
||||
errorInfo,
|
||||
try: false,
|
||||
});
|
||||
// You can also log error messages to an error reporting service here
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.errorInfo) {
|
||||
// Error path
|
||||
return (
|
||||
<div>
|
||||
<h2>Error</h2>
|
||||
{this.state.error && (
|
||||
<>
|
||||
<h3>In: {this.props.namespace ?? 'default'}</h3>
|
||||
<main>{this.state.error.toString()}</main>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Normally, just render children
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ReactBasicErrorBoundary;
|
||||
240
src/ErrorBoundary/ErrorBoundary.tsx
Normal file
240
src/ErrorBoundary/ErrorBoundary.tsx
Normal file
@@ -0,0 +1,240 @@
|
||||
import { Button, Code, Collapse, Group, Paper, rem, Text } from '@mantine/core';
|
||||
import { IconExclamationCircle } from '@tabler/icons-react';
|
||||
import React, { type PropsWithChildren } from 'react';
|
||||
|
||||
let ErrorBoundaryOptions = {
|
||||
disabled: false,
|
||||
onError: undefined,
|
||||
} as {
|
||||
disabled?: boolean;
|
||||
onError?: (error: any, errorInfo: any) => void;
|
||||
};
|
||||
|
||||
export const SetErrorBoundaryOptions = (options: typeof ErrorBoundaryOptions) => {
|
||||
ErrorBoundaryOptions = { ...ErrorBoundaryOptions, ...options };
|
||||
};
|
||||
|
||||
export const GetErrorBoundaryOptions = () => {
|
||||
return { ...ErrorBoundaryOptions };
|
||||
};
|
||||
|
||||
interface ErrorBoundaryProps extends PropsWithChildren {
|
||||
namespace?: string;
|
||||
onReportClick?: () => void;
|
||||
onResetClick?: () => void;
|
||||
onRetryClick?: () => void;
|
||||
reportAPI?: string;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
error: any;
|
||||
errorInfo: any;
|
||||
reported?: boolean;
|
||||
showDetail: boolean;
|
||||
timer?: NodeJS.Timeout | undefined;
|
||||
try: boolean;
|
||||
tryCnt: number;
|
||||
wasReset?: boolean;
|
||||
}
|
||||
|
||||
export class ReactErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
error: null,
|
||||
errorInfo: null,
|
||||
showDetail: false,
|
||||
timer: undefined,
|
||||
try: false,
|
||||
tryCnt: 0,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidCatch(error: any, errorInfo: any) {
|
||||
if (GetErrorBoundaryOptions()?.disabled) {
|
||||
throw Error('ErrorBoundary pass through enabled, rethrowing error', {
|
||||
cause: error,
|
||||
//@ts-ignore
|
||||
info: errorInfo,
|
||||
});
|
||||
}
|
||||
// Catch errors in any components below and re-render with error message
|
||||
this.setState({
|
||||
error,
|
||||
errorInfo,
|
||||
try: false,
|
||||
});
|
||||
if (typeof GetErrorBoundaryOptions()?.onError === 'function') {
|
||||
GetErrorBoundaryOptions()?.onError?.(error, errorInfo);
|
||||
}
|
||||
// You can also log error messages to an error reporting service here
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.errorInfo) {
|
||||
// Error path
|
||||
return (
|
||||
<div>
|
||||
<h2
|
||||
style={{
|
||||
color: 'var(--mantine-color-error)',
|
||||
display: 'flex',
|
||||
gap: '8px',
|
||||
}}
|
||||
>
|
||||
<IconExclamationCircle
|
||||
color="var(--mantine-color-error)"
|
||||
style={{ height: rem(20), width: rem(20) }}
|
||||
/>
|
||||
A react error has occurred!
|
||||
</h2>
|
||||
<h4>You can try one of these solutions to get back on track:</h4>
|
||||
<ul>
|
||||
<li>Report the error to our team</li>
|
||||
<li>Hit the keys Ctrl-Shift-R</li>
|
||||
<li>Make sure your web browser is up to date</li>
|
||||
<li>Clear your browser cache and cookies</li>
|
||||
<li>Try using a different web browser</li>
|
||||
<li>Reset your layout and settings</li>
|
||||
<li>Retry loading the application</li>
|
||||
</ul>
|
||||
|
||||
<Group>
|
||||
<Button
|
||||
color="red"
|
||||
my="md"
|
||||
onClick={() =>
|
||||
this.setState((state) => ({
|
||||
showDetail: !state.showDetail,
|
||||
}))
|
||||
}
|
||||
size="compact-xs"
|
||||
variant="outline"
|
||||
>
|
||||
More
|
||||
</Button>
|
||||
<Button
|
||||
color="blue"
|
||||
disabled={this.state.tryCnt > 2 || this.state.try}
|
||||
my="md"
|
||||
onClick={() => this.retry()}
|
||||
size="compact-xs"
|
||||
variant="outline"
|
||||
>
|
||||
Retry {this.state.tryCnt > 0 ? `(${this.state.tryCnt})` : ''}
|
||||
</Button>
|
||||
<Button
|
||||
color="yellow"
|
||||
disabled={this.state.wasReset}
|
||||
my="md"
|
||||
onClick={() => this.reset()}
|
||||
size="compact-xs"
|
||||
variant="outline"
|
||||
>
|
||||
Reset Layout or Settings
|
||||
</Button>
|
||||
<Button
|
||||
color="green"
|
||||
disabled={this.state.reported}
|
||||
my="md"
|
||||
onClick={() => this.report()}
|
||||
size="compact-xs"
|
||||
variant="outline"
|
||||
>
|
||||
Report
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
{this.state.error && (
|
||||
<Paper
|
||||
p="md"
|
||||
shadow="xs"
|
||||
style={{
|
||||
textAlign: 'justify',
|
||||
}}
|
||||
w="50%"
|
||||
withBorder
|
||||
>
|
||||
{this.props.namespace && <Text size="sm">In: {this.props.namespace}</Text>}
|
||||
<Text size="sm">{this.state.error.toString()}</Text>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
<Collapse in={this.state.showDetail}>
|
||||
<Code block color="rgba(235, 86, 5, 0.6)" mah="100vh" w="100%">
|
||||
{this.state.errorInfo.componentStack}
|
||||
</Code>
|
||||
</Collapse>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
//Retry code, if you do it too many times, it will stop
|
||||
if (this.state.try) {
|
||||
if (!this.state.timer) {
|
||||
const tm = setTimeout(() => {
|
||||
clearTimeout(this.state.timer);
|
||||
this.setState((state) => ({
|
||||
...state,
|
||||
timer: undefined,
|
||||
try: false,
|
||||
}));
|
||||
}, 1000);
|
||||
|
||||
this.setState((state) => ({
|
||||
...state,
|
||||
timer: tm,
|
||||
}));
|
||||
}
|
||||
return <div>Retrying {this.state.tryCnt}...</div>;
|
||||
}
|
||||
|
||||
// Normally, just render children
|
||||
return this.props.children;
|
||||
}
|
||||
|
||||
async report() {
|
||||
if (this.props?.onReportClick) {
|
||||
this.props.onReportClick();
|
||||
this.setState(() => ({
|
||||
reported: true,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
if (this.props?.onResetClick) {
|
||||
this.props.onResetClick();
|
||||
this.setState(() => ({
|
||||
wasReset: true,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(() => ({
|
||||
wasReset: true,
|
||||
}));
|
||||
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
retry() {
|
||||
this.setState({
|
||||
try: true,
|
||||
tryCnt: this.state.tryCnt + 1,
|
||||
});
|
||||
if (this.props?.onRetryClick) {
|
||||
this.props.onRetryClick();
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
error: undefined,
|
||||
errorInfo: undefined,
|
||||
});
|
||||
this.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
export default ReactErrorBoundary;
|
||||
2
src/ErrorBoundary/index.ts
Normal file
2
src/ErrorBoundary/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as ReactBasicErrorBoundary } from './BasicErrorBoundary';
|
||||
export { default as ReactErrorBoundary } from './ErrorBoundary';
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './Boxer';
|
||||
export * from './ErrorBoundary';
|
||||
export * from './Former';
|
||||
export * from './FormerControllers';
|
||||
export * from './Gridler';
|
||||
|
||||
Reference in New Issue
Block a user