From 47bc18415d27a2629d0c95fa508949a93f257cdb Mon Sep 17 00:00:00 2001 From: Mohammed Mehdi <96487647+catosaurusrex2003@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:10:50 +0530 Subject: [PATCH] feat: error boundary in layout to handle application errors / crashes (#1108) Co-authored-by: Cody's Dad <40604284+AceTheCreator@users.noreply.github.com> --- library/package.json | 1 + .../ApplicationErrorHandler/ErrorBoundary.tsx | 39 +++++++++++++++++++ library/src/containers/AsyncApi/Layout.tsx | 38 +++++++++--------- .../src/containers/AsyncApi/Standalone.tsx | 8 +--- library/src/containers/Error/Error.tsx | 5 ++- library/src/types.ts | 2 +- package-lock.json | 13 +++++++ 7 files changed, 77 insertions(+), 29 deletions(-) create mode 100644 library/src/containers/ApplicationErrorHandler/ErrorBoundary.tsx diff --git a/library/package.json b/library/package.json index 9e385c929..36495daf2 100644 --- a/library/package.json +++ b/library/package.json @@ -76,6 +76,7 @@ "isomorphic-dompurify": "^2.14.0", "marked": "^4.0.14", "openapi-sampler": "^1.2.1", + "react-error-boundary": "^4.1.2", "use-resize-observer": "^9.1.0" }, "peerDependencies": { diff --git a/library/src/containers/ApplicationErrorHandler/ErrorBoundary.tsx b/library/src/containers/ApplicationErrorHandler/ErrorBoundary.tsx new file mode 100644 index 000000000..ecefbaf08 --- /dev/null +++ b/library/src/containers/ApplicationErrorHandler/ErrorBoundary.tsx @@ -0,0 +1,39 @@ +import React, { useEffect, useState } from 'react'; +import { ReactNode } from 'react'; +import { ErrorBoundary, FallbackProps } from 'react-error-boundary'; +import { ErrorObject } from '../../types'; +import { Error } from '../Error/Error'; + +interface Props { + children: ReactNode; +} + +function fallbackRender({ error }: FallbackProps) { + const ErrorObject: ErrorObject = { + title: 'Something went wrong', + type: 'application-error', + validationErrors: [ + { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + title: error?.message, + }, + ], + }; + return ; +} + +const AsyncApiErrorBoundary = ({ children }: Props) => { + const [key, setKey] = useState(0); + + useEffect(() => { + setKey((prevKey) => prevKey + 1); + }, [children]); + + return ( + + {children} + + ); +}; + +export default AsyncApiErrorBoundary; diff --git a/library/src/containers/AsyncApi/Layout.tsx b/library/src/containers/AsyncApi/Layout.tsx index 3a6a6702f..b6105b392 100644 --- a/library/src/containers/AsyncApi/Layout.tsx +++ b/library/src/containers/AsyncApi/Layout.tsx @@ -8,22 +8,19 @@ import { Servers } from '../Servers/Servers'; import { Operations } from '../Operations/Operations'; import { Messages } from '../Messages/Messages'; import { Schemas } from '../Schemas/Schemas'; -import { Error } from '../Error/Error'; import { ConfigInterface } from '../../config'; import { SpecificationContext, ConfigContext } from '../../contexts'; -import { ErrorObject } from '../../types'; +import AsyncApiErrorBoundary from '../ApplicationErrorHandler/ErrorBoundary'; interface Props { asyncapi: AsyncAPIDocumentInterface; config: ConfigInterface; - error?: ErrorObject; } const AsyncApiLayout: React.FunctionComponent = ({ asyncapi, config, - error = null, }) => { const [observerClassName, setObserverClassName] = useState('container:xl'); @@ -48,24 +45,25 @@ const AsyncApiLayout: React.FunctionComponent = ({
-
- {configShow.sidebar && } -
-
- {configShow.errors && error && } - {configShow.info && } - {configShow.servers && } - {configShow.operations && } - {configShow.messages && } - {configShow.schemas && } + +
+ {configShow.sidebar && } +
+
+ {configShow.info && } + {configShow.servers && } + {configShow.operations && } + {configShow.messages && } + {configShow.schemas && } +
+
-
-
+
diff --git a/library/src/containers/AsyncApi/Standalone.tsx b/library/src/containers/AsyncApi/Standalone.tsx index 9c5cc3c8e..b4ac3665a 100644 --- a/library/src/containers/AsyncApi/Standalone.tsx +++ b/library/src/containers/AsyncApi/Standalone.tsx @@ -84,13 +84,7 @@ class AsyncApiComponent extends Component { ); } - return ( - - ); + return ; } private updateState(schema: PropsSchema) { diff --git a/library/src/containers/Error/Error.tsx b/library/src/containers/Error/Error.tsx index eba833efa..e22618ea3 100644 --- a/library/src/containers/Error/Error.tsx +++ b/library/src/containers/Error/Error.tsx @@ -15,7 +15,10 @@ const renderErrors = (errors: ValidationError[]): React.ReactNode => { } return (
- {`line ${singleError?.location?.startLine + singleError?.location?.startOffset}:`} + {(singleError?.location?.startLine ?? + singleError?.location?.startOffset) && ( + {`line ${singleError?.location?.startLine + singleError?.location?.startOffset}:`} + )} {singleError.title} diff --git a/library/src/types.ts b/library/src/types.ts index c1d52ae4c..9f58864e2 100644 --- a/library/src/types.ts +++ b/library/src/types.ts @@ -44,7 +44,7 @@ export interface MessageExample { export interface ValidationError { title: string; - location: { + location?: { jsonPointer: string; startLine: number; startColumn: number; diff --git a/package-lock.json b/package-lock.json index b93d8ed72..810d58f86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ "isomorphic-dompurify": "^2.14.0", "marked": "^4.0.14", "openapi-sampler": "^1.2.1", + "react-error-boundary": "^4.1.2", "use-resize-observer": "^9.1.0" }, "devDependencies": { @@ -22049,6 +22050,18 @@ "loose-envify": "^1.1.0" } }, + "node_modules/react-error-boundary": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.1.2.tgz", + "integrity": "sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",