Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(react): type error as unknown in ErrorBoundary #11819

Merged
merged 3 commits into from Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions MIGRATION.md
Expand Up @@ -369,6 +369,7 @@ To make sure these integrations work properly you'll have to change how you
- [AWS Serverless SDK](./MIGRATION.md#aws-serverless-sdk)
- [Ember SDK](./MIGRATION.md#ember-sdk)
- [Svelte SDK](./MIGRATION.md#svelte-sdk)
- [React SDK](./MIGRATION.md#react-sdk)

### General

Expand Down Expand Up @@ -1000,6 +1001,26 @@ const config = {
export default withSentryConfig(config);
```

### React SDK

#### Updated error types to be `unknown` instead of `Error`.

In v8, we are changing the `ErrorBoundary` error types returned from `onError`, `onReset`, `onUnmount`, and
`beforeCapture`. to be `unknown` instead of `Error`. This more accurately matches behaviour of `componentDidCatch`, the
lifecycle method the Sentry `ErrorBoundary` component uses.

As per the [React docs on error boundaries](https://react.dev/reference/react/Component#componentdidcatch):

> error: The `error` that was thrown. In practice, it will usually be an instance of `Error` but this is not guaranteed
> because JavaScript allows to throw any value, including strings or even `null`.

This means you will have to use `instanceof Error` or similar to explicitly make sure that the error thrown was an
instance of `Error`.

The Sentry SDK maintainers also went ahead and made a PR to update the
[TypeScript definitions of `componentDidCatch`](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/69434) for the
React package - this will be released with React 20.

### Gatsby SDK

#### Removal of Gatsby Initialization via plugin options
Expand Down
Expand Up @@ -9,7 +9,7 @@ function App() {
fallback={({ error, componentStack, resetError }) => (
<React.Fragment>
<div>You have encountered an error</div>
<div>{error.toString()}</div>
<div>{`${error}`}</div>
<div>{componentStack}</div>
<button
onClick={() => {
Expand Down
18 changes: 9 additions & 9 deletions packages/react/src/errorboundary.tsx
Expand Up @@ -15,7 +15,7 @@ export function isAtLeastReact17(version: string): boolean {
export const UNKNOWN_COMPONENT = 'unknown';

export type FallbackRender = (errorData: {
error: Error;
error: unknown;
componentStack: string;
eventId: string;
resetError(): void;
Expand All @@ -40,15 +40,15 @@ export type ErrorBoundaryProps = {
*/
fallback?: React.ReactElement | FallbackRender | undefined;
/** Called when the error boundary encounters an error */
onError?: ((error: Error, componentStack: string, 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: Error | null, componentStack: string | null, eventId: string | null) => void) | undefined;
onReset?: ((error: unknown, componentStack: string | null, eventId: string | null) => void) | undefined;
/** Called on componentWillUnmount() */
onUnmount?: ((error: Error | null, componentStack: string | null, 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: Error | null, componentStack: string | null) => void) | undefined;
beforeCapture?: ((scope: Scope, error: unknown, componentStack: string | undefined) => void) | undefined;
};

type ErrorBoundaryState =
Expand All @@ -59,7 +59,7 @@ type ErrorBoundaryState =
}
| {
componentStack: React.ErrorInfo['componentStack'];
error: Error;
error: unknown;
eventId: string;
};

Expand Down Expand Up @@ -118,7 +118,7 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
}
}

public componentDidCatch(error: Error & { cause?: Error }, { componentStack }: React.ErrorInfo): void {
public componentDidCatch(error: unknown, { componentStack }: React.ErrorInfo): void {
const { beforeCapture, onError, showDialog, dialogOptions } = this.props;
withScope(scope => {
// If on React version >= 17, create stack trace from componentStack param and links
Expand Down Expand Up @@ -200,9 +200,9 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
if (typeof fallback === 'function') {
element = React.createElement(fallback, {
error: state.error,
componentStack: state.componentStack,
componentStack: state.componentStack as string,
resetError: this.resetErrorBoundary,
eventId: state.eventId,
eventId: state.eventId as string,
});
} else {
element = fallback;
Expand Down
5 changes: 2 additions & 3 deletions packages/react/test/errorboundary.test.tsx
Expand Up @@ -35,7 +35,7 @@ function Bam(): JSX.Element {
return <Boo title={title} />;
}

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

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

return (
<span>
EffectSpyFallback {counter} - {error.message}
EffectSpyFallback {counter} - {(error as Error).message}
</span>
);
}
Expand All @@ -54,7 +54,6 @@ interface TestAppProps extends ErrorBoundaryProps {
}

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