From df17b72171649690e3e4476fb657fbc9f1e135a1 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 16 Dec 2024 16:35:11 -0500 Subject: [PATCH 1/2] feat(react)!: Update `ErrorBoundary` `componentStack` type --- CHANGELOG.md | 2 +- docs/migration/v8-to-v9.md | 4 ++ packages/react/src/errorboundary.tsx | 65 +++++++++++++--------------- 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27d35f621ed5..419a042b711c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott -Work in this release was contributed by @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, and @mstrokin. Thank you for your contributions! +Work in this release was contributed by @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, and @kunal-511. Thank you for your contributions! - **feat(solidstart)!: Default to `--import` setup and add `autoInjectServerSentry` ([#14862](https://github.com/getsentry/sentry-javascript/pull/14862))** diff --git a/docs/migration/v8-to-v9.md b/docs/migration/v8-to-v9.md index b49a3e8f161e..436385a61a29 100644 --- a/docs/migration/v8-to-v9.md +++ b/docs/migration/v8-to-v9.md @@ -126,6 +126,10 @@ Older Typescript versions _may_ still work, but we will not test them anymore an To customize which files are deleted after upload, define the `filesToDeleteAfterUpload` array with globs. +### `@sentry/react` + +The `componentStack` field in the `ErrorBoundary` component is now typed as `string | undefined` instead of `string | null | undefined`. This more closely matches the actual behavior of React, which always returns a `string` whenever a component stack is available. `undefined` is only returned if no error has been caught by the error boundary. + ### Uncategorized (TODO) TODO diff --git a/packages/react/src/errorboundary.tsx b/packages/react/src/errorboundary.tsx index fa397cb3b9ef..c2dc57a00c11 100644 --- a/packages/react/src/errorboundary.tsx +++ b/packages/react/src/errorboundary.tsx @@ -42,15 +42,15 @@ export type ErrorBoundaryProps = { */ handled?: boolean | undefined; /** Called when the error boundary encounters an error */ - onError?: ((error: unknown, componentStack: string | undefined, eventId: string) => void) | undefined; + onError?: ((error: unknown, componentStack: string, eventId: string) => void) | undefined; /** Called on componentDidMount() */ onMount?: (() => void) | undefined; /** Called if resetError() is called from the fallback render props function */ - onReset?: ((error: unknown, componentStack: string | null | undefined, eventId: string | null) => void) | undefined; + onReset?: ((error: unknown, componentStack: string | null, eventId: string | null) => void) | undefined; /** Called on componentWillUnmount() */ - onUnmount?: ((error: unknown, componentStack: string | null | undefined, eventId: string | null) => void) | undefined; + onUnmount?: ((error: unknown, componentStack: string | null, eventId: string | null) => void) | undefined; /** Called before the error is captured by Sentry, allows for you to add tags or context using the scope */ - beforeCapture?: ((scope: Scope, error: unknown, componentStack: string | undefined) => void) | undefined; + beforeCapture?: ((scope: Scope, error: unknown, componentStack: string) => void) | undefined; }; type ErrorBoundaryState = @@ -65,7 +65,7 @@ type ErrorBoundaryState = eventId: string; }; -const INITIAL_STATE = { +const INITIAL_STATE: ErrorBoundaryState = { componentStack: null, error: null, eventId: null, @@ -104,20 +104,17 @@ class ErrorBoundary extends React.Component { if (beforeCapture) { - beforeCapture(scope, error, passedInComponentStack); + beforeCapture(scope, error, componentStack); } const handled = this.props.handled != null ? this.props.handled : !!this.props.fallback; const eventId = captureReactException(error, errorInfo, { mechanism: { handled } }); if (onError) { - onError(error, passedInComponentStack, eventId); + onError(error, componentStack, eventId); } if (showDialog) { this._lastEventId = eventId; @@ -165,35 +162,33 @@ class ErrorBoundary extends React.Component this.resetErrorBoundary(), + eventId: state.eventId, + }) + : fallback; + + if (React.isValidElement(element)) { + return element; } - if (typeof children === 'function') { - return (children as () => React.ReactNode)(); + if (fallback) { + DEBUG_BUILD && logger.warn('fallback did not produce a valid ReactElement'); } - return children; + + // Fail gracefully if no fallback provided or is not valid + return null; } } From 04f617840739f58d95332cb5672e43e736c08028 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 13 Jan 2025 22:43:36 -0500 Subject: [PATCH 2/2] refactor this a little bit more --- docs/migration/v8-to-v9.md | 4 +++- packages/react/src/errorboundary.tsx | 36 +++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/docs/migration/v8-to-v9.md b/docs/migration/v8-to-v9.md index 436385a61a29..5ca6fe2a4962 100644 --- a/docs/migration/v8-to-v9.md +++ b/docs/migration/v8-to-v9.md @@ -128,7 +128,9 @@ Older Typescript versions _may_ still work, but we will not test them anymore an ### `@sentry/react` -The `componentStack` field in the `ErrorBoundary` component is now typed as `string | undefined` instead of `string | null | undefined`. This more closely matches the actual behavior of React, which always returns a `string` whenever a component stack is available. `undefined` is only returned if no error has been caught by the error boundary. +The `componentStack` field in the `ErrorBoundary` component is now typed as `string` instead of `string | null | undefined` for the `onError` and `onReset` lifecycle methods. This more closely matches the actual behavior of React, which always returns a `string` whenever a component stack is available. + +In the `onUnmount` lifecycle method, the `componentStack` field is now typed as `string | null`. The `componentStack` is `null` when no error has been thrown at time of unmount. ### Uncategorized (TODO) diff --git a/packages/react/src/errorboundary.tsx b/packages/react/src/errorboundary.tsx index c2dc57a00c11..9925ef1a8308 100644 --- a/packages/react/src/errorboundary.tsx +++ b/packages/react/src/errorboundary.tsx @@ -17,6 +17,11 @@ export type FallbackRender = (errorData: { resetError(): void; }) => React.ReactElement; +type OnUnmountType = { + (error: null, componentStack: null, eventId: null): void; + (error: unknown, componentStack: string, eventId: string): void; +}; + export type ErrorBoundaryProps = { children?: React.ReactNode | (() => React.ReactNode); /** If a Sentry report dialog should be rendered on error */ @@ -45,10 +50,18 @@ export type ErrorBoundaryProps = { onError?: ((error: unknown, componentStack: string, eventId: string) => void) | undefined; /** Called on componentDidMount() */ onMount?: (() => void) | undefined; - /** Called if resetError() is called from the fallback render props function */ - onReset?: ((error: unknown, componentStack: string | null, eventId: string | null) => void) | undefined; - /** Called on componentWillUnmount() */ - onUnmount?: ((error: unknown, componentStack: string | null, eventId: string | null) => void) | undefined; + /** + * Called when the error boundary resets due to a reset call from the + * fallback render props function. + */ + onReset?: ((error: unknown, componentStack: string, eventId: string) => void) | undefined; + /** + * Called on componentWillUnmount() with the error, componentStack, and eventId. + * + * If the error boundary never encountered an error, the error + * componentStack, and eventId will be null. + */ + onUnmount?: OnUnmountType | undefined; /** Called before the error is captured by Sentry, allows for you to add tags or context using the scope */ beforeCapture?: ((scope: Scope, error: unknown, componentStack: string) => void) | undefined; }; @@ -140,7 +153,15 @@ class ErrorBoundary extends React.Component