Skip to content

Commit a3e9eae

Browse files
authored
chore: use @stoplight/react-error-boundary (#55)
* feat: use @stoplight/react-error-boundary * chore: require node 10 * test: dive
1 parent 2b51ce3 commit a3e9eae

File tree

5 files changed

+70
-76
lines changed

5 files changed

+70
-76
lines changed

.circleci/config.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
version: 2
22

33
jobs:
4-
test_node_8:
4+
test_node_10:
55
docker:
6-
- image: circleci/node:8
6+
- image: circleci/node:10
77
steps:
88
- checkout
99
- run:
@@ -24,7 +24,7 @@ jobs:
2424
2525
release:
2626
docker:
27-
- image: circleci/node:8
27+
- image: circleci/node:10
2828
steps:
2929
- checkout
3030
- run: yarn
@@ -37,10 +37,10 @@ workflows:
3737
version: 2
3838
test_and_release:
3939
jobs:
40-
- test_node_8
40+
- test_node_10
4141
- release:
4242
filters:
4343
branches:
4444
only: master
4545
requires:
46-
- test_node_8
46+
- test_node_10

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"**/*"
1818
],
1919
"engines": {
20-
"node": ">=8.3.0"
20+
"node": ">=10.0"
2121
},
2222
"scripts": {
2323
"build": "sl-scripts build --sourcemap",
@@ -44,6 +44,7 @@
4444
},
4545
"dependencies": {
4646
"@stoplight/json": "^3.1.1",
47+
"@stoplight/react-error-boundary": "^1.0.0",
4748
"classnames": "^2.2.6",
4849
"json-schema-merge-allof": "https://github.com/stoplightio/json-schema-merge-allof",
4950
"lodash-es": "^4.17.15",

src/components/JsonSchemaViewer.tsx

Lines changed: 12 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ErrorBoundaryForwardedProps, FallbackComponent, withErrorBoundary } from '@stoplight/react-error-boundary';
12
import { TreeStore } from '@stoplight/tree-list';
23
import { Intent, Spinner } from '@stoplight/ui-kit';
34
import cn from 'classnames';
@@ -14,8 +15,6 @@ import { renderSchema } from '../utils/renderSchema';
1415
import { ComputeSchemaMessageData, isRenderedSchemaMessage } from '../workers/messages';
1516
import { SchemaTree } from './SchemaTree';
1617

17-
export type FallbackComponent = React.ComponentType<{ error: Error | null }>;
18-
1918
export interface IJsonSchemaViewer {
2019
schema: JSONSchema4;
2120
dereferencedSchema?: JSONSchema4;
@@ -29,15 +28,11 @@ export interface IJsonSchemaViewer {
2928
maxRows?: number;
3029
onGoToRef?: GoToRefHandler;
3130
mergeAllOf?: boolean;
32-
FallbackComponent?: FallbackComponent;
31+
FallbackComponent?: typeof FallbackComponent;
3332
rowRenderer?: RowRenderer;
3433
}
3534

36-
export interface IJsonSchemaViewerComponent extends Omit<IJsonSchemaViewer, 'FallbackComponent'> {
37-
onError(err: Error): void;
38-
}
39-
40-
export class JsonSchemaViewerComponent extends React.PureComponent<IJsonSchemaViewerComponent> {
35+
export class JsonSchemaViewerComponent extends React.PureComponent<IJsonSchemaViewer & ErrorBoundaryForwardedProps> {
4136
protected treeStore: TreeStore;
4237
protected instanceId: string;
4338
public static schemaWorker?: WebWorker;
@@ -46,7 +41,7 @@ export class JsonSchemaViewerComponent extends React.PureComponent<IJsonSchemaVi
4641
computing: false,
4742
};
4843

49-
constructor(props: IJsonSchemaViewerComponent) {
44+
constructor(props: IJsonSchemaViewer & ErrorBoundaryForwardedProps) {
5045
super(props);
5146

5247
this.treeStore = new TreeStore({
@@ -81,8 +76,8 @@ export class JsonSchemaViewerComponent extends React.PureComponent<IJsonSchemaVi
8176
this.setState({ computing: false });
8277
if (data.error === null) {
8378
this.treeStore.nodes = data.nodes;
84-
} else {
85-
this.props.onError(new Error(data.error));
79+
} else if (this.props.boundaryRef.current !== null) {
80+
this.props.boundaryRef.current.throwError(new Error(data.error));
8681
}
8782
}
8883
});
@@ -205,7 +200,7 @@ export class JsonSchemaViewerComponent extends React.PureComponent<IJsonSchemaVi
205200
}
206201
}
207202

208-
const JsonSchemaFallbackComponent: FallbackComponent = ({ error }) => {
203+
const JsonSchemaFallbackComponent: typeof FallbackComponent = ({ error }) => {
209204
return (
210205
<div className="p-4">
211206
<b className="text-danger">Error</b>
@@ -214,36 +209,8 @@ const JsonSchemaFallbackComponent: FallbackComponent = ({ error }) => {
214209
);
215210
};
216211

217-
// react-error-boundary does not support recovering, see https://github.com/bvaughn/react-error-boundary/pull/16/files
218-
export class JsonSchemaViewer extends React.PureComponent<IJsonSchemaViewer, { error: null | Error }> {
219-
public state = {
220-
error: null,
221-
};
222-
223-
public componentDidUpdate(prevProps: Readonly<IJsonSchemaViewer>) {
224-
if (
225-
this.state.error !== null &&
226-
(prevProps.schema !== this.props.schema || prevProps.dereferencedSchema !== this.props.dereferencedSchema)
227-
) {
228-
this.setState({ error: null });
229-
}
230-
}
231-
232-
// there is no error hook yet, see https://reactjs.org/docs/hooks-faq.html#how-do-lifecycle-methods-correspond-to-hooks
233-
public static getDerivedStateFromError(error: Error) {
234-
return { error };
235-
}
236-
237-
private onError: IJsonSchemaViewerComponent['onError'] = error => {
238-
this.setState({ error });
239-
};
240-
241-
public render() {
242-
const { FallbackComponent: Fallback = JsonSchemaFallbackComponent, ...props } = this.props;
243-
if (this.state.error) {
244-
return <Fallback error={this.state.error} />;
245-
}
246-
247-
return <JsonSchemaViewerComponent {...props} onError={this.onError} />;
248-
}
249-
}
212+
export const JsonSchemaViewer = withErrorBoundary<IJsonSchemaViewer>(JsonSchemaViewerComponent, {
213+
FallbackComponent: JsonSchemaFallbackComponent,
214+
recoverableProps: ['schema', 'dereferencedSchema'],
215+
reportErrors: false,
216+
});

src/components/__tests__/JsonSchemaViewer.spec.tsx

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { shallow } from 'enzyme';
1+
import { mount, shallow } from 'enzyme';
22
import 'jest-enzyme';
33
import * as React from 'react';
44

55
import { JSONSchema4 } from 'json-schema';
6-
import { JsonSchemaViewerComponent, SchemaTree } from '../../components';
6+
import { JsonSchemaViewer, SchemaTree } from '../../components';
77
import { isSchemaViewerEmpty } from '../../utils/isSchemaViewerEmpty';
88
import { renderSchema } from '../../utils/renderSchema';
99

@@ -47,21 +47,25 @@ describe('JSON Schema Viewer component', () => {
4747

4848
test('should render empty message if schema is empty', () => {
4949
(isSchemaViewerEmpty as jest.Mock).mockReturnValue(true);
50-
const wrapper = shallow(<JsonSchemaViewerComponent schema={{}} onError={jest.fn()} />);
50+
const wrapper = shallow(<JsonSchemaViewer schema={{}} onError={jest.fn()} />)
51+
.dive()
52+
.dive();
5153
expect(isSchemaViewerEmpty).toHaveBeenCalledWith({});
5254
expect(wrapper.find(SchemaTree)).not.toExist();
5355
});
5456

5557
test('should render SchemaView if schema is provided', () => {
56-
const wrapper = shallow(<JsonSchemaViewerComponent schema={schema as JSONSchema4} onError={jest.fn()} />);
58+
const wrapper = shallow(<JsonSchemaViewer schema={schema as JSONSchema4} onError={jest.fn()} />)
59+
.dive()
60+
.dive();
5761
expect(isSchemaViewerEmpty).toHaveBeenCalledWith(schema);
5862
expect(wrapper.find(SchemaTree)).toExist();
5963
});
6064

6165
test('should not perform full processing in a worker if provided schema has fewer nodes than maxRows', () => {
62-
const wrapper = shallow(
63-
<JsonSchemaViewerComponent schema={schema as JSONSchema4} maxRows={10} onError={jest.fn()} />,
64-
);
66+
const wrapper = shallow(<JsonSchemaViewer schema={schema as JSONSchema4} maxRows={10} onError={jest.fn()} />)
67+
.dive()
68+
.dive();
6569
expect(SchemaWorker.prototype.postMessage).not.toHaveBeenCalled();
6670
expect(wrapper.instance()).toHaveProperty('treeStore.nodes.length', 4);
6771
});
@@ -81,7 +85,9 @@ describe('JSON Schema Viewer component', () => {
8185
],
8286
};
8387

84-
shallow(<JsonSchemaViewerComponent schema={schemaAllOf} maxRows={10} onError={jest.fn()} />);
88+
shallow(<JsonSchemaViewer schema={schemaAllOf} maxRows={10} onError={jest.fn()} />)
89+
.dive()
90+
.dive();
8591

8692
expect(SchemaWorker.prototype.postMessage).toHaveBeenCalledWith({
8793
instanceId: expect.any(String),
@@ -105,15 +111,17 @@ describe('JSON Schema Viewer component', () => {
105111
],
106112
};
107113

108-
shallow(<JsonSchemaViewerComponent schema={schemaAllOf} maxRows={10} mergeAllOf={false} onError={jest.fn()} />);
114+
shallow(<JsonSchemaViewer schema={schemaAllOf} maxRows={10} mergeAllOf={false} onError={jest.fn()} />)
115+
.dive()
116+
.dive();
109117

110118
expect(SchemaWorker.prototype.postMessage).not.toHaveBeenCalledWith();
111119
});
112120

113121
test('should pre-render maxRows nodes and perform full processing in a worker if provided schema has more nodes than maxRows', () => {
114-
const wrapper = shallow(
115-
<JsonSchemaViewerComponent schema={schema as JSONSchema4} maxRows={1} onError={jest.fn()} />,
116-
);
122+
const wrapper = shallow(<JsonSchemaViewer schema={schema as JSONSchema4} maxRows={1} onError={jest.fn()} />)
123+
.dive()
124+
.dive();
117125
expect(SchemaWorker.prototype.postMessage).toHaveBeenCalledWith({
118126
instanceId: expect.any(String),
119127
mergeAllOf: true,
@@ -166,17 +174,17 @@ describe('JSON Schema Viewer component', () => {
166174

167175
test('should render all nodes on main thread when worker cannot be spawned regardless of maxRows or schema', () => {
168176
SchemaWorker.prototype.isShim = true;
169-
const wrapper = shallow(
170-
<JsonSchemaViewerComponent schema={schema as JSONSchema4} maxRows={1} onError={jest.fn()} />,
171-
);
177+
const wrapper = shallow(<JsonSchemaViewer schema={schema as JSONSchema4} maxRows={1} onError={jest.fn()} />)
178+
.dive()
179+
.dive();
172180

173181
expect(SchemaWorker.prototype.postMessage).not.toHaveBeenCalled();
174182
expect(wrapper.instance()).toHaveProperty('treeStore.nodes.length', 4);
175183
});
176184

177185
test('should handle exceptions that may occur during full rendering', () => {
178186
const onError = jest.fn();
179-
shallow(<JsonSchemaViewerComponent schema={schema as JSONSchema4} maxRows={1} onError={onError} />);
187+
const wrapper = mount(<JsonSchemaViewer schema={schema as JSONSchema4} maxRows={1} onError={onError} />);
180188

181189
SchemaWorker.prototype.addEventListener.mock.calls[0][1]({
182190
data: {
@@ -186,13 +194,14 @@ describe('JSON Schema Viewer component', () => {
186194
},
187195
});
188196

189-
expect(onError).toHaveBeenCalledWith(new Error('error occurred'));
197+
expect(onError).toHaveBeenCalledWith(new Error('error occurred'), null);
198+
wrapper.unmount();
190199
});
191200

192201
test('should not apply result of full processing in a worker if instance ids do not match', () => {
193-
const wrapper = shallow(
194-
<JsonSchemaViewerComponent schema={schema as JSONSchema4} maxRows={0} onError={jest.fn()} />,
195-
);
202+
const wrapper = shallow(<JsonSchemaViewer schema={schema as JSONSchema4} maxRows={0} onError={jest.fn()} />)
203+
.dive()
204+
.dive();
196205
expect(SchemaWorker.prototype.postMessage).toHaveBeenCalledWith({
197206
instanceId: expect.any(String),
198207
mergeAllOf: true,
@@ -212,15 +221,18 @@ describe('JSON Schema Viewer component', () => {
212221
});
213222

214223
test('should create one shared instance of schema worker one mounted for a first time and keep it', () => {
215-
const worker = (shallow(
216-
<JsonSchemaViewerComponent schema={schema as JSONSchema4} maxRows={0} onError={jest.fn()} />,
217-
).instance()!.constructor as any).schemaWorker;
224+
const worker = (shallow(<JsonSchemaViewer schema={schema as JSONSchema4} maxRows={0} onError={jest.fn()} />)
225+
.dive()
226+
.dive()
227+
.instance()!.constructor as any).schemaWorker;
218228

219229
expect(SchemaWorker).toHaveBeenCalledTimes(1);
220230

221231
expect(worker).toBe(
222-
(shallow(<JsonSchemaViewerComponent schema={schema as JSONSchema4} maxRows={0} onError={jest.fn()} />).instance()!
223-
.constructor as any).schemaWorker,
232+
(shallow(<JsonSchemaViewer schema={schema as JSONSchema4} maxRows={0} onError={jest.fn()} />)
233+
.dive()
234+
.dive()
235+
.instance()!.constructor as any).schemaWorker,
224236
);
225237

226238
expect(SchemaWorker).toHaveBeenCalledTimes(1);

yarn.lock

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1851,6 +1851,13 @@
18511851
unified "~8.3.2"
18521852
unist-util-select "~2.0"
18531853

1854+
"@stoplight/react-error-boundary@^1.0.0":
1855+
version "1.0.0"
1856+
resolved "https://registry.yarnpkg.com/@stoplight/react-error-boundary/-/react-error-boundary-1.0.0.tgz#758dbee1ae3afa8f2cc42f6fc82aefc9d4efaa67"
1857+
integrity sha512-UH5ZyNqzQp8w/fm8cpq0tmsc63xtMBFmfp7g6ZYW4+/Bhnm/T1RhRpG/5d7/3ZkMLiJ7avsoslbHJ2JU6geEDQ==
1858+
dependencies:
1859+
"@stoplight/types" "^11.1.0"
1860+
18541861
"@stoplight/scripts@7.0.4":
18551862
version "7.0.4"
18561863
resolved "https://registry.yarnpkg.com/@stoplight/scripts/-/scripts-7.0.4.tgz#0099f9f7ef8e2e480bc38cc646d196e15f4c33d2"
@@ -1941,6 +1948,13 @@
19411948
dependencies:
19421949
"@types/json-schema" "^7.0.3"
19431950

1951+
"@stoplight/types@^11.1.0":
1952+
version "11.1.1"
1953+
resolved "https://registry.yarnpkg.com/@stoplight/types/-/types-11.1.1.tgz#a92d1833adb580a72439f42ba73de8f560fd68b4"
1954+
integrity sha512-IU8U9y/uO548z15DX/Jl053u9VQG8gCwNtypuD4RtskUA7pvHZl4+zzGK3klgIcO6Ql3Jk4/fcrFaN9vjmdEWg==
1955+
dependencies:
1956+
"@types/json-schema" "^7.0.3"
1957+
19441958
"@stoplight/ui-kit@^2.10.0":
19451959
version "2.10.0"
19461960
resolved "https://registry.yarnpkg.com/@stoplight/ui-kit/-/ui-kit-2.10.0.tgz#fb19fae334349572e4dbe4f10bd2148d10bd0fbd"

0 commit comments

Comments
 (0)