-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(react): Add Error Boundary component #2647
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
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
9ac6c67
ref(react): Update name for Profiler
AbhiPrasad 40c23d1
feat(react): Add initial error boundary component
AbhiPrasad 4688e63
chore(react): use @testing-library/react
AbhiPrasad 3e9c8f9
feat(react): Setup basic error boundary props
AbhiPrasad 271f895
feat(react): Add lifecycle hooks to errorboundary
AbhiPrasad 053cce4
feat(react): Add report dialog
AbhiPrasad 01d12a3
feat(react): Add error boundary HOC
AbhiPrasad 23501b6
feat(react): Add render key option
AbhiPrasad 332e566
chore(react): update CHANGELOG
AbhiPrasad 494a180
fix(react): use new captureException API
AbhiPrasad 1ea4a26
fix(react): set componentStack properly
AbhiPrasad c8db669
ref(react): Don't throw error when no fallback given
AbhiPrasad e8ba768
ref(react): Remove renderKey
AbhiPrasad 34dfbcd
ref(react): Overload fallback prop
AbhiPrasad 8db807a
chore: Remove comment
AbhiPrasad 8ee6baf
ci: add ignore pattern for node 6
AbhiPrasad 5174791
cd: ignore react on node 6
AbhiPrasad File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import * as Sentry from '@sentry/browser'; | ||
import * as hoistNonReactStatic from 'hoist-non-react-statics'; | ||
import * as React from 'react'; | ||
|
||
export const UNKNOWN_COMPONENT = 'unknown'; | ||
|
||
export type FallbackRender = (fallback: { | ||
error: Error | null; | ||
componentStack: string | null; | ||
resetError(): void; | ||
}) => React.ReactNode; | ||
|
||
export type ErrorBoundaryProps = { | ||
showDialog?: boolean; | ||
dialogOptions?: Sentry.ReportDialogOptions; | ||
// tslint:disable-next-line: no-null-undefined-union | ||
fallback?: React.ReactNode | FallbackRender; | ||
onError?(error: Error, componentStack: string): void; | ||
onMount?(): void; | ||
onReset?(error: Error | null, componentStack: string | null): void; | ||
onUnmount?(error: Error | null, componentStack: string | null): void; | ||
}; | ||
|
||
type ErrorBoundaryState = { | ||
componentStack: string | null; | ||
error: Error | null; | ||
}; | ||
|
||
const INITIAL_STATE = { | ||
componentStack: null, | ||
error: null, | ||
}; | ||
|
||
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> { | ||
public state: ErrorBoundaryState = INITIAL_STATE; | ||
|
||
public componentDidCatch(error: Error, { componentStack }: React.ErrorInfo): void { | ||
Sentry.captureException(error, { contexts: { react: { componentStack } } }); | ||
const { onError, showDialog, dialogOptions } = this.props; | ||
if (onError) { | ||
onError(error, componentStack); | ||
} | ||
if (showDialog) { | ||
Sentry.showReportDialog(dialogOptions); | ||
} | ||
|
||
// componentDidCatch is used over getDerivedStateFromError | ||
// so that componentStack is accessible through state. | ||
this.setState({ error, componentStack }); | ||
} | ||
|
||
public componentDidMount(): void { | ||
const { onMount } = this.props; | ||
if (onMount) { | ||
onMount(); | ||
} | ||
} | ||
|
||
public componentWillUnmount(): void { | ||
const { error, componentStack } = this.state; | ||
const { onUnmount } = this.props; | ||
if (onUnmount) { | ||
onUnmount(error, componentStack); | ||
} | ||
} | ||
|
||
public resetErrorBoundary = () => { | ||
const { onReset } = this.props; | ||
if (onReset) { | ||
onReset(this.state.error, this.state.componentStack); | ||
} | ||
this.setState(INITIAL_STATE); | ||
}; | ||
|
||
public render(): React.ReactNode { | ||
const { fallback } = this.props; | ||
const { error, componentStack } = this.state; | ||
|
||
if (error) { | ||
if (React.isValidElement(fallback)) { | ||
return fallback; | ||
} | ||
if (typeof fallback === 'function') { | ||
return fallback({ error, componentStack, resetError: this.resetErrorBoundary }) as FallbackRender; | ||
} | ||
|
||
// Fail gracefully if no fallback provided | ||
return null; | ||
} | ||
|
||
return this.props.children; | ||
} | ||
} | ||
|
||
function withErrorBoundary<P extends object>( | ||
WrappedComponent: React.ComponentType<P>, | ||
errorBoundaryOptions: ErrorBoundaryProps, | ||
): React.FC<P> { | ||
const componentDisplayName = WrappedComponent.displayName || WrappedComponent.name || UNKNOWN_COMPONENT; | ||
|
||
const Wrapped: React.FC<P> = (props: P) => ( | ||
<ErrorBoundary {...errorBoundaryOptions}> | ||
<WrappedComponent {...props} /> | ||
</ErrorBoundary> | ||
); | ||
|
||
Wrapped.displayName = `errorBoundary(${componentDisplayName})`; | ||
|
||
// Copy over static methods from Wrapped component to Profiler HOC | ||
// See: https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over | ||
hoistNonReactStatic(Wrapped, WrappedComponent); | ||
return Wrapped; | ||
} | ||
|
||
export { ErrorBoundary, withErrorBoundary }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from '@sentry/browser'; | ||
|
||
export { Profiler, withProfiler } from './profiler'; | ||
export { ErrorBoundary, withErrorBoundary } from './errorboundary'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.