Skip to content

Commit ac6ac07

Browse files
authored
feat(react): Add a handled prop to ErrorBoundary (#14560)
The previous behaviour was to rely on the presence of the `fallback` prop to decide if the error was considered handled or not. The new property lets users explicitly choose what should the handled status be. If omitted, the old behaviour is still applied.
1 parent 04711c2 commit ac6ac07

File tree

2 files changed

+49
-42
lines changed

2 files changed

+49
-42
lines changed

packages/react/src/errorboundary.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ export type ErrorBoundaryProps = {
3535
*
3636
*/
3737
fallback?: React.ReactElement | FallbackRender | undefined;
38+
/**
39+
* If set to `true` or `false`, the error `handled` property will be set to the given value.
40+
* If unset, the default behaviour is to rely on the presence of the `fallback` prop to determine
41+
* if the error was handled or not.
42+
*/
43+
handled?: boolean | undefined;
3844
/** Called when the error boundary encounters an error */
3945
onError?: ((error: unknown, componentStack: string | undefined, eventId: string) => void) | undefined;
4046
/** Called on componentDidMount() */
@@ -107,7 +113,8 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
107113
beforeCapture(scope, error, passedInComponentStack);
108114
}
109115

110-
const eventId = captureReactException(error, errorInfo, { mechanism: { handled: !!this.props.fallback } });
116+
const handled = this.props.handled != null ? this.props.handled : !!this.props.fallback;
117+
const eventId = captureReactException(error, errorInfo, { mechanism: { handled } });
111118

112119
if (onError) {
113120
onError(error, passedInComponentStack, eventId);

packages/react/test/errorboundary.test.tsx

+41-41
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { fireEvent, render, screen } from '@testing-library/react';
44
import * as React from 'react';
55
import { useState } from 'react';
66

7-
import type { ErrorBoundaryProps } from '../src/errorboundary';
7+
import type { ErrorBoundaryProps, FallbackRender } from '../src/errorboundary';
88
import { ErrorBoundary, UNKNOWN_COMPONENT, withErrorBoundary } from '../src/errorboundary';
99

1010
const mockCaptureException = jest.fn();
@@ -537,47 +537,47 @@ describe('ErrorBoundary', () => {
537537
expect(mockOnReset).toHaveBeenCalledTimes(1);
538538
expect(mockOnReset).toHaveBeenCalledWith(expect.any(Error), expect.any(String), expect.any(String));
539539
});
540+
it.each`
541+
fallback | handled | expected
542+
${true} | ${undefined} | ${true}
543+
${false} | ${undefined} | ${false}
544+
${true} | ${false} | ${false}
545+
${true} | ${true} | ${true}
546+
${false} | ${true} | ${true}
547+
${false} | ${false} | ${false}
548+
`(
549+
'sets `handled: $expected` when `handled` is $handled and `fallback` is $fallback',
550+
async ({
551+
fallback,
552+
handled,
553+
expected,
554+
}: {
555+
fallback: boolean;
556+
handled: boolean | undefined;
557+
expected: boolean;
558+
}) => {
559+
const fallbackComponent: FallbackRender | undefined = fallback
560+
? ({ resetError }) => <button data-testid="reset" onClick={resetError} />
561+
: undefined;
562+
render(
563+
<TestApp handled={handled} fallback={fallbackComponent}>
564+
<h1>children</h1>
565+
</TestApp>,
566+
);
540567

541-
it('sets `handled: true` when a fallback is provided', async () => {
542-
render(
543-
<TestApp fallback={({ resetError }) => <button data-testid="reset" onClick={resetError} />}>
544-
<h1>children</h1>
545-
</TestApp>,
546-
);
547-
548-
expect(mockCaptureException).toHaveBeenCalledTimes(0);
549-
550-
const btn = screen.getByTestId('errorBtn');
551-
fireEvent.click(btn);
552-
553-
expect(mockCaptureException).toHaveBeenCalledTimes(1);
554-
expect(mockCaptureException).toHaveBeenLastCalledWith(expect.any(Object), {
555-
captureContext: {
556-
contexts: { react: { componentStack: expect.any(String) } },
557-
},
558-
mechanism: { handled: true },
559-
});
560-
});
561-
562-
it('sets `handled: false` when no fallback is provided', async () => {
563-
render(
564-
<TestApp>
565-
<h1>children</h1>
566-
</TestApp>,
567-
);
568-
569-
expect(mockCaptureException).toHaveBeenCalledTimes(0);
570-
571-
const btn = screen.getByTestId('errorBtn');
572-
fireEvent.click(btn);
568+
expect(mockCaptureException).toHaveBeenCalledTimes(0);
573569

574-
expect(mockCaptureException).toHaveBeenCalledTimes(1);
575-
expect(mockCaptureException).toHaveBeenLastCalledWith(expect.any(Object), {
576-
captureContext: {
577-
contexts: { react: { componentStack: expect.any(String) } },
578-
},
579-
mechanism: { handled: false },
580-
});
581-
});
570+
const btn = screen.getByTestId('errorBtn');
571+
fireEvent.click(btn);
572+
573+
expect(mockCaptureException).toHaveBeenCalledTimes(1);
574+
expect(mockCaptureException).toHaveBeenLastCalledWith(expect.any(Object), {
575+
captureContext: {
576+
contexts: { react: { componentStack: expect.any(String) } },
577+
},
578+
mechanism: { handled: expected },
579+
});
580+
},
581+
);
582582
});
583583
});

0 commit comments

Comments
 (0)