Skip to content

Commit df17b72

Browse files
committed
feat(react)!: Update ErrorBoundary componentStack type
1 parent 9f74bc9 commit df17b72

File tree

3 files changed

+35
-36
lines changed

3 files changed

+35
-36
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
1212

13-
Work in this release was contributed by @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, and @mstrokin. Thank you for your contributions!
13+
Work in this release was contributed by @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, and @kunal-511. Thank you for your contributions!
1414

1515
- **feat(solidstart)!: Default to `--import` setup and add `autoInjectServerSentry` ([#14862](https://github.com/getsentry/sentry-javascript/pull/14862))**
1616

docs/migration/v8-to-v9.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ Older Typescript versions _may_ still work, but we will not test them anymore an
126126

127127
To customize which files are deleted after upload, define the `filesToDeleteAfterUpload` array with globs.
128128

129+
### `@sentry/react`
130+
131+
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.
132+
129133
### Uncategorized (TODO)
130134

131135
TODO

packages/react/src/errorboundary.tsx

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ export type ErrorBoundaryProps = {
4242
*/
4343
handled?: boolean | undefined;
4444
/** Called when the error boundary encounters an error */
45-
onError?: ((error: unknown, componentStack: string | undefined, eventId: string) => void) | undefined;
45+
onError?: ((error: unknown, componentStack: string, eventId: string) => void) | undefined;
4646
/** Called on componentDidMount() */
4747
onMount?: (() => void) | undefined;
4848
/** Called if resetError() is called from the fallback render props function */
49-
onReset?: ((error: unknown, componentStack: string | null | undefined, eventId: string | null) => void) | undefined;
49+
onReset?: ((error: unknown, componentStack: string | null, eventId: string | null) => void) | undefined;
5050
/** Called on componentWillUnmount() */
51-
onUnmount?: ((error: unknown, componentStack: string | null | undefined, eventId: string | null) => void) | undefined;
51+
onUnmount?: ((error: unknown, componentStack: string | null, eventId: string | null) => void) | undefined;
5252
/** Called before the error is captured by Sentry, allows for you to add tags or context using the scope */
53-
beforeCapture?: ((scope: Scope, error: unknown, componentStack: string | undefined) => void) | undefined;
53+
beforeCapture?: ((scope: Scope, error: unknown, componentStack: string) => void) | undefined;
5454
};
5555

5656
type ErrorBoundaryState =
@@ -65,7 +65,7 @@ type ErrorBoundaryState =
6565
eventId: string;
6666
};
6767

68-
const INITIAL_STATE = {
68+
const INITIAL_STATE: ErrorBoundaryState = {
6969
componentStack: null,
7070
error: null,
7171
eventId: null,
@@ -104,20 +104,17 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
104104

105105
public componentDidCatch(error: unknown, errorInfo: React.ErrorInfo): void {
106106
const { componentStack } = errorInfo;
107-
// TODO(v9): Remove this check and type `componentStack` to be React.ErrorInfo['componentStack'].
108-
const passedInComponentStack: string | undefined = componentStack == null ? undefined : componentStack;
109-
110107
const { beforeCapture, onError, showDialog, dialogOptions } = this.props;
111108
withScope(scope => {
112109
if (beforeCapture) {
113-
beforeCapture(scope, error, passedInComponentStack);
110+
beforeCapture(scope, error, componentStack);
114111
}
115112

116113
const handled = this.props.handled != null ? this.props.handled : !!this.props.fallback;
117114
const eventId = captureReactException(error, errorInfo, { mechanism: { handled } });
118115

119116
if (onError) {
120-
onError(error, passedInComponentStack, eventId);
117+
onError(error, componentStack, eventId);
121118
}
122119
if (showDialog) {
123120
this._lastEventId = eventId;
@@ -165,35 +162,33 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
165162
const { fallback, children } = this.props;
166163
const state = this.state;
167164

168-
if (state.error) {
169-
let element: React.ReactElement | undefined = undefined;
170-
if (typeof fallback === 'function') {
171-
element = React.createElement(fallback, {
172-
error: state.error,
173-
componentStack: state.componentStack as string,
174-
resetError: this.resetErrorBoundary.bind(this),
175-
eventId: state.eventId as string,
176-
});
177-
} else {
178-
element = fallback;
179-
}
180-
181-
if (React.isValidElement(element)) {
182-
return element;
183-
}
184-
185-
if (fallback) {
186-
DEBUG_BUILD && logger.warn('fallback did not produce a valid ReactElement');
187-
}
165+
// `componentStack` is only null in the initial state, when no error has been captured.
166+
// If an error has been captured, `componentStack` will be a string.
167+
// We cannot check `state.error` because null can be thrown as an error.
168+
if (state.componentStack === null) {
169+
return typeof children === 'function' ? children() : children;
170+
}
188171

189-
// Fail gracefully if no fallback provided or is not valid
190-
return null;
172+
const element =
173+
typeof fallback === 'function'
174+
? React.createElement(fallback, {
175+
error: state.error,
176+
componentStack: state.componentStack,
177+
resetError: () => this.resetErrorBoundary(),
178+
eventId: state.eventId,
179+
})
180+
: fallback;
181+
182+
if (React.isValidElement(element)) {
183+
return element;
191184
}
192185

193-
if (typeof children === 'function') {
194-
return (children as () => React.ReactNode)();
186+
if (fallback) {
187+
DEBUG_BUILD && logger.warn('fallback did not produce a valid ReactElement');
195188
}
196-
return children;
189+
190+
// Fail gracefully if no fallback provided or is not valid
191+
return null;
197192
}
198193
}
199194

0 commit comments

Comments
 (0)