Skip to content

Commit 6fbf235

Browse files
authored
fix(editor-preview): reset error boundaries on editor content change (#4347)
1 parent 8dfe48f commit 6fbf235

File tree

4 files changed

+133
-20
lines changed

4 files changed

+133
-20
lines changed

src/App.jsx

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,26 +24,9 @@ import EditorContentOriginPlugin from './plugins/editor-content-origin/index.js'
2424
import EditorContentTypePlugin from './plugins/editor-content-type/index.js';
2525
import EditorContentPersistencePlugin from './plugins/editor-content-persistence/index.js';
2626
import EditorContentFixturesPlugin from './plugins/editor-content-fixtures/index.js';
27+
import EditorSafeRenderPlugin from './plugins/editor-safe-render/index.js';
2728
import SwaggerUIAdapterPlugin from './plugins/swagger-ui-adapter/index.js';
2829

29-
const SafeRenderPlugin = (system) =>
30-
SwaggerUI.plugins.SafeRender({
31-
componentList: [
32-
'TopBar',
33-
'SwaggerEditorLayout',
34-
'Editor',
35-
'EditorTextarea',
36-
'EditorMonaco',
37-
'EditorPane',
38-
'EditorPaneBarTop',
39-
'EditorPreviewPane',
40-
'ValidationPane',
41-
'AlertDialog',
42-
'ConfirmDialog',
43-
'Dropzone',
44-
],
45-
})(system);
46-
4730
const SwaggerEditor = React.memo((props) => {
4831
const mergedProps = deepmerge(SwaggerEditor.defaultProps, props);
4932

@@ -73,6 +56,7 @@ SwaggerEditor.plugins = {
7356
EditorPreviewSwaggerUI: EditorPreviewSwaggerUIPlugin,
7457
EditorPreviewAsyncAPI: EditorPreviewAsyncAPIPlugin,
7558
EditorPreviewApiDesignSystems: EditorPreviewApiDesignSystemsPlugin,
59+
EditorSafeRender: EditorSafeRenderPlugin,
7660
TopBar: TopBarPlugin,
7761
SplashScreenPlugin,
7862
Layout: LayoutPlugin,
@@ -98,7 +82,7 @@ SwaggerEditor.presets = {
9882
TopBarPlugin,
9983
SplashScreenPlugin,
10084
LayoutPlugin,
101-
SafeRenderPlugin,
85+
EditorSafeRenderPlugin,
10286
],
10387
monaco: () => [
10488
ModalsPlugin,
@@ -121,7 +105,7 @@ SwaggerEditor.presets = {
121105
TopBarPlugin,
122106
SplashScreenPlugin,
123107
LayoutPlugin,
124-
SafeRenderPlugin,
108+
EditorSafeRenderPlugin,
125109
],
126110
default: (...args) => SwaggerEditor.presets.monaco(...args),
127111
};
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import PropTypes from 'prop-types';
2+
import React, { Component } from 'react';
3+
4+
class ErrorBoundary extends Component {
5+
static defaultState = { hasError: false, error: null, editorContent: null };
6+
7+
static getDerivedStateFromError(error) {
8+
return { hasError: true, error };
9+
}
10+
11+
constructor(...args) {
12+
super(...args);
13+
this.state = this.constructor.defaultState;
14+
}
15+
16+
componentDidMount() {
17+
const { editorSelectors } = this.props;
18+
19+
this.setState({ editorContent: editorSelectors.selectContent() });
20+
}
21+
22+
componentDidUpdate(prevProps, prevState) {
23+
const { editorSelectors } = this.props;
24+
const hasEditorContentChanged = prevState.editorContent !== editorSelectors.selectContent();
25+
26+
if (!hasEditorContentChanged) return;
27+
28+
const newState = { editorContent: editorSelectors.selectContent() };
29+
30+
if (prevState.hasError) {
31+
newState.hasError = false;
32+
newState.error = null;
33+
}
34+
35+
this.setState(newState);
36+
}
37+
38+
componentDidCatch(error, errorInfo) {
39+
const {
40+
fn: { componentDidCatch },
41+
} = this.props;
42+
43+
componentDidCatch(error, errorInfo);
44+
}
45+
46+
render() {
47+
const { hasError, error } = this.state;
48+
const { getComponent, targetName, children } = this.props;
49+
50+
if (hasError && error) {
51+
const FallbackComponent = getComponent('Fallback');
52+
return <FallbackComponent name={targetName} />;
53+
}
54+
55+
return children;
56+
}
57+
}
58+
ErrorBoundary.propTypes = {
59+
targetName: PropTypes.string,
60+
getComponent: PropTypes.func.isRequired,
61+
fn: PropTypes.shape({
62+
componentDidCatch: PropTypes.func.isRequired,
63+
}).isRequired,
64+
editorSelectors: PropTypes.shape({
65+
selectContent: PropTypes.func.isRequired,
66+
}).isRequired,
67+
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
68+
};
69+
ErrorBoundary.defaultProps = {
70+
targetName: 'this component',
71+
children: null,
72+
};
73+
74+
export default ErrorBoundary;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import SwaggerUI from 'swagger-ui-react';
2+
3+
import ErrorBoundaryWrapper from './wrap-components/ErrorBoundaryWrapper.jsx';
4+
5+
/**
6+
* This is special version of SwaggerUI.plugins.SafeRender.
7+
* In editor context, we want to dismiss the error produced
8+
* in error boundary if editor content has changed.
9+
*/
10+
const EditorSafeRenderPlugin = () => {
11+
const safeRenderPlugin = () =>
12+
SwaggerUI.plugins.SafeRender({
13+
fullOverride: true,
14+
componentList: [
15+
'TopBar',
16+
'SwaggerEditorLayout',
17+
'Editor',
18+
'EditorTextarea',
19+
'EditorMonaco',
20+
'EditorPane',
21+
'EditorPaneBarTop',
22+
'EditorPreviewPane',
23+
'ValidationPane',
24+
'AlertDialog',
25+
'ConfirmDialog',
26+
'Dropzone',
27+
],
28+
});
29+
30+
const safeRenderPluginOverride = () => ({
31+
wrapComponents: {
32+
ErrorBoundary: ErrorBoundaryWrapper,
33+
},
34+
});
35+
36+
return [safeRenderPlugin, safeRenderPluginOverride];
37+
};
38+
39+
export default EditorSafeRenderPlugin;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React from 'react';
2+
3+
import ErrorBoundary from '../components/ErrorBoundary.jsx';
4+
5+
const ErrorBoundaryWrapper = (Original, system) => {
6+
const ErrorBoundaryOverride = (props) => {
7+
const { editorSelectors } = system;
8+
9+
// eslint-disable-next-line react/jsx-props-no-spreading
10+
return <ErrorBoundary {...props} editorSelectors={editorSelectors} />;
11+
};
12+
13+
return ErrorBoundaryOverride;
14+
};
15+
16+
export default ErrorBoundaryWrapper;

0 commit comments

Comments
 (0)