Skip to content

Commit 7759d18

Browse files
AbhiPrasadmydea
andauthored
feat(react): type error as unknown in ErrorBoundary (#11819)
Fixes #11728 As reporting in the above issue, it is not typesafe to type the error thrown by react components as `Error`, it can actually be any JS object/primitive that was thrown. This means we have to type everything as `unknown`. This change only will happen for `8.x` because it's a breaking change. Related: - https://react.dev/reference/react/Component#componentdidcatch - DefinitelyTyped/DefinitelyTyped#69434 --------- Co-authored-by: Francesco Novy <[email protected]>
1 parent d381ace commit 7759d18

File tree

4 files changed

+33
-13
lines changed

4 files changed

+33
-13
lines changed

MIGRATION.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ To make sure these integrations work properly you'll have to change how you
369369
- [AWS Serverless SDK](./MIGRATION.md#aws-serverless-sdk)
370370
- [Ember SDK](./MIGRATION.md#ember-sdk)
371371
- [Svelte SDK](./MIGRATION.md#svelte-sdk)
372+
- [React SDK](./MIGRATION.md#react-sdk)
372373

373374
### General
374375

@@ -1000,6 +1001,26 @@ const config = {
10001001
export default withSentryConfig(config);
10011002
```
10021003

1004+
### React SDK
1005+
1006+
#### Updated error types to be `unknown` instead of `Error`.
1007+
1008+
In v8, we are changing the `ErrorBoundary` error types returned from `onError`, `onReset`, `onUnmount`, and
1009+
`beforeCapture`. to be `unknown` instead of `Error`. This more accurately matches behaviour of `componentDidCatch`, the
1010+
lifecycle method the Sentry `ErrorBoundary` component uses.
1011+
1012+
As per the [React docs on error boundaries](https://react.dev/reference/react/Component#componentdidcatch):
1013+
1014+
> error: The `error` that was thrown. In practice, it will usually be an instance of `Error` but this is not guaranteed
1015+
> because JavaScript allows to throw any value, including strings or even `null`.
1016+
1017+
This means you will have to use `instanceof Error` or similar to explicitly make sure that the error thrown was an
1018+
instance of `Error`.
1019+
1020+
The Sentry SDK maintainers also went ahead and made a PR to update the
1021+
[TypeScript definitions of `componentDidCatch`](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/69434) for the
1022+
React package - this will be released with React 20.
1023+
10031024
### Gatsby SDK
10041025

10051026
#### Removal of Gatsby Initialization via plugin options

dev-packages/e2e-tests/test-applications/create-react-app/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ function App() {
99
fallback={({ error, componentStack, resetError }) => (
1010
<React.Fragment>
1111
<div>You have encountered an error</div>
12-
<div>{error.toString()}</div>
12+
<div>{`${error}`}</div>
1313
<div>{componentStack}</div>
1414
<button
1515
onClick={() => {

packages/react/src/errorboundary.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function isAtLeastReact17(version: string): boolean {
1515
export const UNKNOWN_COMPONENT = 'unknown';
1616

1717
export type FallbackRender = (errorData: {
18-
error: Error;
18+
error: unknown;
1919
componentStack: string;
2020
eventId: string;
2121
resetError(): void;
@@ -40,15 +40,15 @@ export type ErrorBoundaryProps = {
4040
*/
4141
fallback?: React.ReactElement | FallbackRender | undefined;
4242
/** Called when the error boundary encounters an error */
43-
onError?: ((error: Error, componentStack: string, eventId: string) => void) | undefined;
43+
onError?: ((error: unknown, componentStack: string, eventId: string) => void) | undefined;
4444
/** Called on componentDidMount() */
4545
onMount?: (() => void) | undefined;
4646
/** Called if resetError() is called from the fallback render props function */
47-
onReset?: ((error: Error | null, componentStack: string | null, eventId: string | null) => void) | undefined;
47+
onReset?: ((error: unknown, componentStack: string | null, eventId: string | null) => void) | undefined;
4848
/** Called on componentWillUnmount() */
49-
onUnmount?: ((error: Error | null, componentStack: string | null, eventId: string | null) => void) | undefined;
49+
onUnmount?: ((error: unknown, componentStack: string | null, eventId: string | null) => void) | undefined;
5050
/** Called before the error is captured by Sentry, allows for you to add tags or context using the scope */
51-
beforeCapture?: ((scope: Scope, error: Error | null, componentStack: string | null) => void) | undefined;
51+
beforeCapture?: ((scope: Scope, error: unknown, componentStack: string | undefined) => void) | undefined;
5252
};
5353

5454
type ErrorBoundaryState =
@@ -59,7 +59,7 @@ type ErrorBoundaryState =
5959
}
6060
| {
6161
componentStack: React.ErrorInfo['componentStack'];
62-
error: Error;
62+
error: unknown;
6363
eventId: string;
6464
};
6565

@@ -118,7 +118,7 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
118118
}
119119
}
120120

121-
public componentDidCatch(error: Error & { cause?: Error }, { componentStack }: React.ErrorInfo): void {
121+
public componentDidCatch(error: unknown, { componentStack }: React.ErrorInfo): void {
122122
const { beforeCapture, onError, showDialog, dialogOptions } = this.props;
123123
withScope(scope => {
124124
// If on React version >= 17, create stack trace from componentStack param and links
@@ -200,9 +200,9 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
200200
if (typeof fallback === 'function') {
201201
element = React.createElement(fallback, {
202202
error: state.error,
203-
componentStack: state.componentStack,
203+
componentStack: state.componentStack as string,
204204
resetError: this.resetErrorBoundary,
205-
eventId: state.eventId,
205+
eventId: state.eventId as string,
206206
});
207207
} else {
208208
element = fallback;

packages/react/test/errorboundary.test.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function Bam(): JSX.Element {
3535
return <Boo title={title} />;
3636
}
3737

38-
function EffectSpyFallback({ error }: { error: Error }): JSX.Element {
38+
function EffectSpyFallback({ error }: { error: unknown }): JSX.Element {
3939
const [counter, setCounter] = useState(0);
4040

4141
React.useEffect(() => {
@@ -44,7 +44,7 @@ function EffectSpyFallback({ error }: { error: Error }): JSX.Element {
4444

4545
return (
4646
<span>
47-
EffectSpyFallback {counter} - {error.message}
47+
EffectSpyFallback {counter} - {(error as Error).message}
4848
</span>
4949
);
5050
}
@@ -54,7 +54,6 @@ interface TestAppProps extends ErrorBoundaryProps {
5454
}
5555

5656
const TestApp: React.FC<TestAppProps> = ({ children, errorComp, ...props }) => {
57-
// eslint-disable-next-line no-param-reassign
5857
const customErrorComp = errorComp || <Bam />;
5958
const [isError, setError] = React.useState(false);
6059
return (

0 commit comments

Comments
 (0)