Skip to content

Commit 677a6f8

Browse files
authored
feat: include FallbackComponent in ErrorBoundaryProvider (#4)
* feat: include FallbackComponent in ErrorBoundaryProvider * chore: pr feedback
1 parent 2c3e1a4 commit 677a6f8

File tree

5 files changed

+82
-52
lines changed

5 files changed

+82
-52
lines changed

src/ErrorBoundary.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ export class ErrorBoundary<P extends object = {}> extends React.PureComponent<
3939
if (this.props.reportErrors !== false) {
4040
try {
4141
if (errorInfo !== null) {
42-
this.context.error(error.message, { errorInfo });
42+
this.context.reporter.error(error.message, { errorInfo });
4343
} else {
44-
this.context.error(error);
44+
this.context.reporter.error(error);
4545
}
4646
} catch (ex) {
4747
console.error('Error could not be reported', ex);
@@ -76,7 +76,7 @@ export class ErrorBoundary<P extends object = {}> extends React.PureComponent<
7676

7777
public render() {
7878
const {
79-
props: { FallbackComponent: Fallback = FallbackComponent, children },
79+
props: { FallbackComponent: Fallback = this.context.FallbackComponent || FallbackComponent, children },
8080
state: { error, componentStack },
8181
} = this;
8282

src/ErrorBoundaryProvider.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
import { ICoreReportingAPI } from '@stoplight/reporter';
22
import * as React from 'react';
3+
import { FallbackComponent } from './FallbackComponent';
4+
import { FallbackProps } from './types';
35

4-
export const ErrorBoundaryContext = React.createContext<ICoreReportingAPI>(console);
6+
export type ErrorBoundaryContext = {
7+
reporter: ICoreReportingAPI;
8+
FallbackComponent?: React.ElementType<FallbackProps>;
9+
};
510

6-
export const ErrorBoundaryProvider: React.FunctionComponent<{ reporter: ICoreReportingAPI }> = ({
7-
reporter,
8-
children,
9-
}) => <ErrorBoundaryContext.Provider value={reporter}>{children}</ErrorBoundaryContext.Provider>;
11+
export const ErrorBoundaryContext = React.createContext<ErrorBoundaryContext>({
12+
reporter: console,
13+
FallbackComponent,
14+
});
15+
16+
export const ErrorBoundaryProvider: React.FunctionComponent<ErrorBoundaryContext> = ({ children, ...props }) => (
17+
<ErrorBoundaryContext.Provider value={props}>{children}</ErrorBoundaryContext.Provider>
18+
);

src/__tests__/ErrorBoundary.test.tsx

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -115,26 +115,73 @@ describe('ErrorBoundary component', () => {
115115
});
116116

117117
describe('and a custom fallback component is provided', () => {
118-
it('renders it pass error-related props', () => {
119-
const CustomFallbackComponent = () => <div>foo</div>;
120-
121-
const wrapper = mount(
122-
<ErrorBoundaryProvider reporter={reporter}>
123-
<ErrorBoundary FallbackComponent={CustomFallbackComponent}>
124-
<TestComponent value={0} />
125-
</ErrorBoundary>
126-
</ErrorBoundaryProvider>,
127-
);
118+
describe('at the props level', () => {
119+
it('renders it pass error-related props', () => {
120+
const CustomFallbackComponent = () => <div>foo</div>;
121+
122+
const wrapper = mount(
123+
<ErrorBoundaryProvider reporter={reporter}>
124+
<ErrorBoundary FallbackComponent={CustomFallbackComponent}>
125+
<TestComponent value={0} />
126+
</ErrorBoundary>
127+
</ErrorBoundaryProvider>,
128+
);
129+
130+
expect(wrapper.find(FallbackComponent)).not.toExist(); // makes sure we don't render the original one
131+
expect(wrapper.find(CustomFallbackComponent)).toExist();
132+
expect(wrapper.find(CustomFallbackComponent)).toHaveProp({
133+
error: ex,
134+
componentStack: expect.stringContaining('in TestComponent'),
135+
tryRecovering: (wrapper.find(ErrorBoundary).instance() as any).recover,
136+
});
137+
138+
wrapper.unmount();
139+
});
140+
});
128141

129-
expect(wrapper.find(FallbackComponent)).not.toExist(); // makes sure we don't render the original one
130-
expect(wrapper.find(CustomFallbackComponent)).toExist();
131-
expect(wrapper.find(CustomFallbackComponent)).toHaveProp({
132-
error: ex,
133-
componentStack: expect.stringContaining('in TestComponent'),
134-
tryRecovering: (wrapper.find(ErrorBoundary).instance() as any).recover,
142+
describe('at the provider level', () => {
143+
it('renders it pass error-related props', () => {
144+
const CustomFallbackComponent = () => <div>foo</div>;
145+
146+
const wrapper = mount(
147+
<ErrorBoundaryProvider reporter={reporter} FallbackComponent={CustomFallbackComponent}>
148+
>
149+
<ErrorBoundary>
150+
<TestComponent value={0} />
151+
</ErrorBoundary>
152+
</ErrorBoundaryProvider>,
153+
);
154+
155+
expect(wrapper.find(FallbackComponent)).not.toExist(); // makes sure we don't render the original one
156+
expect(wrapper.find(CustomFallbackComponent)).toExist();
157+
expect(wrapper.find(CustomFallbackComponent)).toHaveProp({
158+
error: ex,
159+
componentStack: expect.stringContaining('in TestComponent'),
160+
tryRecovering: (wrapper.find(ErrorBoundary).instance() as any).recover,
161+
});
162+
163+
wrapper.unmount();
135164
});
165+
});
136166

137-
wrapper.unmount();
167+
describe('at both the provider and props level', () => {
168+
it('prefers the props one', () => {
169+
const CustomFallbackContextComponent = () => <div>context!</div>;
170+
const CustomFallbackPropsComponent = () => <div>level!</div>;
171+
172+
const wrapper = mount(
173+
<ErrorBoundaryProvider reporter={reporter} FallbackComponent={CustomFallbackContextComponent}>
174+
<ErrorBoundary FallbackComponent={CustomFallbackPropsComponent}>
175+
<TestComponent value={0} />
176+
</ErrorBoundary>
177+
</ErrorBoundaryProvider>,
178+
);
179+
180+
expect(wrapper.find(FallbackComponent)).not.toExist(); // makes sure we don't render the original one
181+
expect(wrapper.find(CustomFallbackContextComponent)).not.toExist();
182+
expect(wrapper.find(CustomFallbackPropsComponent)).toExist();
183+
wrapper.unmount();
184+
});
138185
});
139186

140187
it('fallback component can try to recover', () => {

src/hooks/__tests__/useReporter.test.ts

Lines changed: 0 additions & 26 deletions
This file was deleted.

src/hooks/useReporter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ import { useContext } from 'react';
33
import { ErrorBoundaryContext } from '../ErrorBoundaryProvider';
44

55
export const useReporter = (): ICoreReportingAPI => {
6-
return useContext<ICoreReportingAPI>(ErrorBoundaryContext);
6+
return useContext(ErrorBoundaryContext).reporter;
77
};

0 commit comments

Comments
 (0)