ReactJS . 5 min read
ReactJS error boundary + Sentry
Understanding error boundary feature
Thinking and building in React involve approaching application design in chunks or components. Every part of your application that performs an action should be treated as a component.
Before React 16, JavaScript errors inside components were used to corrupt React’s internal state and cause it to emit cryptic errors on the next renders. These errors were always caused by an earlier error in the application code, but React did not provide a way to handle them gracefully in components, and could not recover from them.
These kinds of render-based errors cannot be caught since React components are declarative. Hence, you can’t just throw in a try…catch block inside a component. A JavaScript error in a part of the UI shouldn’t break the whole app. To solve this problem Luckily, the Error Boundary feature was introduced in React 16. According to React Documentation, Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.
Basically, error boundaries are React Components. Error boundaries catch JavaScript errors anywhere in their child component tree. By wrapping the whole tree up in an ErrorBoundaryComponent, we can catch any JavaScript errors that occur in its child components. An error boundary can’t catch an error within itself.
Error boundaries do not catch errors for:
- Event handlers
- Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
- Server side rendering
- Errors thrown in the error boundary itself (rather than its children)
Now let’s learn how to use error boundary
A class component becomes an error boundary if it defines either (or both) of the lifecycle methods static getDerivedStateFromError() or componentDidCatch(). Use static getDerivedStateFromError() to render a fallback UI after an error has been thrown. Use componentDidCatch() to log error information.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, info) {
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
Then you can use it as a regular component:
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
Let’s implement this in a demo application
Step 1: Create a react project, run:
npx create-react-app my-app
cd my-app
npm start
Step 2: Create a buggy component
In this component we are creating a simple click counter, when the click limit reaches count 5, it throws an error.
import React from 'react';
class BuggyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {counter: 0};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(({counter}) => ({
counter: counter + 1
}));
}
render() {
if (this.state.counter === 5) {
// Simulate a JS error
throw new Error('I crashed!');
}
return <h1 onClick={this.handleClick}>Click counter - {this.state.counter}</h1>;
}
}
export default BuggyComponent;
Step 3: Create an error boundary component
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {error: null, errorInfo: null, eventId: null};
}
componentDidCatch(error, errorInfo) {
// Catch errors in any components below and re-render with error message
this.setState({
error: error,
errorInfo: errorInfo,
})
// You can also log error messages to an error reporting service here
}
render() {
if (this.state.errorInfo) {
// Error path
return (
<div>
<h2>Something went wrong.</h2>
<details style={{whiteSpace: 'pre-wrap'}}>
{this.state.error && this.state.error.toString()}
<br/>
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
// Normally, just render children
return this.props.children;
}
}
export default ErrorBoundary;
Step 4: Use error boundary in App.js
Open the app.js file of the project and add BuggyComponent inside it. In the example, we are going to use BuggyComponent instances, one with Error Boundary as its parent component and one without Error Boundary.
import React from 'react';
import logo from './logo.svg';
import './App.css';
import ErrorBoundary from "./ErrorBoundary";
import BuggyComponent from "./BuggyComponent";
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo"/>
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
<p>Buddy counter with error boundary</p>
<ErrorBoundary>
<BuggyComponent/>
</ErrorBoundary>
<p>Buddy counter without error boundary</p>
<BuggyComponent/>
</header>
</div>
);
}
export default App;
Bonus Step: Adding Sentry
Too many applications take a casual approach to error detection, handling, and reporting because developers view it as either drudgery or unimportant. But it is really important if you want your product to work flawlessly.
What is Sentry and why we are using it?
Sentry is Open-source error tracking that helps developers to monitor, fix crashes in real time. Don’t forget about boosting the efficiency, improving user experience. Sentry has support for JavaScript, Node, Python, PHP, Ruby, Java and other programming languages. You can get more information here.
To use Sentry with your React application, you will need to use @sentry/browser
(Sentry’s browser JavaScript SDK).
Add the @sentry/browser
to your project:
npm install --save @sentry/browser
Connecting the SDK to Sentry
After you’ve completed setting up a project in Sentry, Sentry will give you a value which we call a DSN or Data Source Name. We have to use that DSN value as an environment variable. You should init the Sentry browser SDK as soon as possible during your application load up, before initializing React. In our case, it would be index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import * as Sentry from '@sentry/browser';
Sentry.init({
dsn: process.env.REACT_APP_SENTRY_DSN,
environment: process.env.NODE_ENV
});
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
Now let’s send all the errors that our error boundary catches to Sentry using Sentry.captureException
. This is also a great opportunity to collect user feedback by using Sentry.showReportDialog
.
import React from "react";
import * as Sentry from '@sentry/browser';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {error: null, errorInfo: null, eventId: null};
}
componentDidCatch(error, errorInfo) {
// Catch errors in any components below and re-render with error message
this.setState({
error: error,
errorInfo: errorInfo,
})
Sentry.withScope(scope => {
scope.setExtras(errorInfo);
const eventId = Sentry.captureException(error);
this.setState({eventId})
});
// You can also log error messages to an error reporting service here
}
render() {
if (this.state.errorInfo) {
// Error path
return (
<div>
<h2>Something went wrong.</h2>
<details style={{whiteSpace: 'pre-wrap'}}>
{this.state.error && this.state.error.toString()}
<br/>
{this.state.errorInfo.componentStack}
</details>
<button onClick={() => Sentry.showReportDialog({eventId: this.state.eventId})}>Report feedback</button>
</div>
);
}
// Normally, just render children
return this.props.children;
}
}
export default ErrorBoundary;
You can find all the source code in this repository Github.
Hey!
Need help with software development or extending your team? You’re in the right place.
Let’s make cool things happen 🚀