Skip to content

Commit 47bc184

Browse files
feat: error boundary in layout to handle application errors / crashes (#1108)
Co-authored-by: Cody's Dad <40604284+AceTheCreator@users.noreply.github.com>
1 parent 950140c commit 47bc184

File tree

7 files changed

+77
-29
lines changed

7 files changed

+77
-29
lines changed

library/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"isomorphic-dompurify": "^2.14.0",
7777
"marked": "^4.0.14",
7878
"openapi-sampler": "^1.2.1",
79+
"react-error-boundary": "^4.1.2",
7980
"use-resize-observer": "^9.1.0"
8081
},
8182
"peerDependencies": {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { ReactNode } from 'react';
3+
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
4+
import { ErrorObject } from '../../types';
5+
import { Error } from '../Error/Error';
6+
7+
interface Props {
8+
children: ReactNode;
9+
}
10+
11+
function fallbackRender({ error }: FallbackProps) {
12+
const ErrorObject: ErrorObject = {
13+
title: 'Something went wrong',
14+
type: 'application-error',
15+
validationErrors: [
16+
{
17+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
18+
title: error?.message,
19+
},
20+
],
21+
};
22+
return <Error error={ErrorObject} />;
23+
}
24+
25+
const AsyncApiErrorBoundary = ({ children }: Props) => {
26+
const [key, setKey] = useState(0);
27+
28+
useEffect(() => {
29+
setKey((prevKey) => prevKey + 1);
30+
}, [children]);
31+
32+
return (
33+
<ErrorBoundary key={key} fallbackRender={fallbackRender}>
34+
{children}
35+
</ErrorBoundary>
36+
);
37+
};
38+
39+
export default AsyncApiErrorBoundary;

library/src/containers/AsyncApi/Layout.tsx

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,19 @@ import { Servers } from '../Servers/Servers';
88
import { Operations } from '../Operations/Operations';
99
import { Messages } from '../Messages/Messages';
1010
import { Schemas } from '../Schemas/Schemas';
11-
import { Error } from '../Error/Error';
1211

1312
import { ConfigInterface } from '../../config';
1413
import { SpecificationContext, ConfigContext } from '../../contexts';
15-
import { ErrorObject } from '../../types';
14+
import AsyncApiErrorBoundary from '../ApplicationErrorHandler/ErrorBoundary';
1615

1716
interface Props {
1817
asyncapi: AsyncAPIDocumentInterface;
1918
config: ConfigInterface;
20-
error?: ErrorObject;
2119
}
2220

2321
const AsyncApiLayout: React.FunctionComponent<Props> = ({
2422
asyncapi,
2523
config,
26-
error = null,
2724
}) => {
2825
const [observerClassName, setObserverClassName] = useState('container:xl');
2926

@@ -48,24 +45,25 @@ const AsyncApiLayout: React.FunctionComponent<Props> = ({
4845
<ConfigContext.Provider value={config}>
4946
<SpecificationContext.Provider value={asyncapi}>
5047
<section className="aui-root">
51-
<div
52-
className={`${observerClassName} relative md:flex bg-white leading-normal`}
53-
id={config.schemaID ?? undefined}
54-
ref={ref}
55-
>
56-
{configShow.sidebar && <Sidebar />}
57-
<div className="panel--center relative py-8 flex-1">
58-
<div className="relative z-10">
59-
{configShow.errors && error && <Error error={error} />}
60-
{configShow.info && <Info />}
61-
{configShow.servers && <Servers />}
62-
{configShow.operations && <Operations />}
63-
{configShow.messages && <Messages />}
64-
{configShow.schemas && <Schemas />}
48+
<AsyncApiErrorBoundary>
49+
<div
50+
className={`${observerClassName} relative md:flex bg-white leading-normal`}
51+
id={config.schemaID ?? undefined}
52+
ref={ref}
53+
>
54+
{configShow.sidebar && <Sidebar />}
55+
<div className="panel--center relative py-8 flex-1">
56+
<div className="relative z-10">
57+
{configShow.info && <Info />}
58+
{configShow.servers && <Servers />}
59+
{configShow.operations && <Operations />}
60+
{configShow.messages && <Messages />}
61+
{configShow.schemas && <Schemas />}
62+
</div>
63+
<div className="panel--right absolute top-0 right-0 h-full bg-gray-800" />
6564
</div>
66-
<div className="panel--right absolute top-0 right-0 h-full bg-gray-800" />
6765
</div>
68-
</div>
66+
</AsyncApiErrorBoundary>
6967
</section>
7068
</SpecificationContext.Provider>
7169
</ConfigContext.Provider>

library/src/containers/AsyncApi/Standalone.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,7 @@ class AsyncApiComponent extends Component<AsyncApiProps, AsyncAPIState> {
8484
);
8585
}
8686

87-
return (
88-
<AsyncApiLayout
89-
asyncapi={asyncapi}
90-
config={concatenatedConfig}
91-
error={error}
92-
/>
93-
);
87+
return <AsyncApiLayout asyncapi={asyncapi} config={concatenatedConfig} />;
9488
}
9589

9690
private updateState(schema: PropsSchema) {

library/src/containers/Error/Error.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ const renderErrors = (errors: ValidationError[]): React.ReactNode => {
1515
}
1616
return (
1717
<div key={index} className="flex gap-2">
18-
<span>{`line ${singleError?.location?.startLine + singleError?.location?.startOffset}:`}</span>
18+
{(singleError?.location?.startLine ??
19+
singleError?.location?.startOffset) && (
20+
<span>{`line ${singleError?.location?.startLine + singleError?.location?.startOffset}:`}</span>
21+
)}
1922
<code className="whitespace-pre-wrap break-all ml-2">
2023
{singleError.title}
2124
</code>

library/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export interface MessageExample {
4444

4545
export interface ValidationError {
4646
title: string;
47-
location: {
47+
location?: {
4848
jsonPointer: string;
4949
startLine: number;
5050
startColumn: number;

package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)