33
Handling errors gracefully in react with error boundaries
try
and catch
blocks of JSX ecosystem.const App = () => {
return (
<div>
<h1>Counter Example</h1>
<ErrorBoundary fallBackUIComponent={<FallBackUI />}>
<BuggyComponent />
</ErrorBoundary>
</div>
);
}
ErrorBoundary
component is placed as a parent to a component which we suspect might cause an error.BuggyComponent
the nearest error boundary which is ErrorBoundary
component catches it and displays a fallback UI. Below Gif will explain this scenario.
ErrorBoundary
:

Before Implementing the error boundary we should keep in mind the following things:
- Error boundary is always a
class
based component. - It uses following two methods to catch the errors:
-
static getDerivedStateFromError()
: A static method which is executed before the DOM is ready(during the rendering phase of the component). This will get invoked whenever descendant component throws an error. -
componentDidCatch()
: This will get invoked whenever a descendant component throws an error. This component is called duringcommit
phase i.e. When the DOM is ready. It can be used to perform side-effects in the component. It receives two parameters:-
error
- error that is being thrown. -
info
- An object with componentStack which tells us which component threw an error.
-
-
Now we can move towards the implementation of the error boundary. Below code will demonstrate a class based react error boundary:
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, errorInfo) {
//Can be used to log to any logging service like sentry
console.log("Catched error", errorInfo);
}
render(){
if(this.state.hasError){
return(
// <h3>Something went wrong!</h3>
//Can be a static or a fall-back component passed as a prop.
this.props.fallBackUIComponent
);
}
return this.props.children;
}
}
Few things to note in the above implementation:
getDerivedStateFromError
returns a value to update the state of the component in this case hasError
is set to true.componentDidCatch
will also catch the error along with the stack trace of the error. This will occur on the commit phase of the component.Lastly in the
render
function if the hasError
state is true
then this will print our fallback component which we passed it as a prop. Else it will return the children
.Usage of this component is fairly simple. Just wrap the compontent in the question with the
ErrorBoundary
Component so that it catches the error thrown by it's descendant. Below example will give you a clear idea of it's usage://Component for fallback UI:
const FallBackUI = () => {
return (
<>
<h3>Something went wrong</h3>
</>
);
}
const BuggyComponent = () => {
const [count, setCount] = React.useState(0);
const increaseCounter = () => {
setCount(preVal => preVal + 1);
}
if(count === 5) {
throw new Error("Crashing the app!!");
}
return (
<>
<div className="counter--block">
<span>Counter</span>
<span>{count}</span>
</div>
<button onClick={increaseCounter}>Increase count</button>
</>
);
}
const App = () => {
return (
<div>
<h1>Counter Example</h1>
<ErrorBoundary fallBackUIComponent={<FallBackUI />}>
<BuggyComponent />
</ErrorBoundary>
</div>
);
}
ReactDOM.render(
<App />
,
document.getElementById("root")
);
Below mentioned errors are not being catched by react's error boundaries:
Event handlers (learn more)
Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
Server side rendering
Errors thrown in the error boundary itself (rather than its children)
There might be couple of reasons for
error boundary
not to work.Some of them are mentioned below:
There are some cases where we forget that the component needs to be always wrapped with the
ErrorBoundary
component so that it catches error. Below example will provide clear understanding:Consider a component which will throw an error when the counter value reaches
5
:const BuggyComponent = () => {
const [count, setCount] = React.useState(0);
const increaseCounter = () => {
setCount(preVal => preVal + 1);
}
if(count === 5) {
throw new Error("Crashing the app!!");
}
return (
<>
<div className="counter--block">
<span>Counter</span>
<span>{count}</span>
</div>
<button onClick={increaseCounter}>Increase count</button>
</>
);
}
Placing the error boundary like below will never allow the
ErrorBoundary
Component to catch error, since the BuggyComponent
is not being wrapped with ErrorBoundary
but rather the content of this component is wrapped with ErrorBoundary
.return (
<ErrorBoundary>
<div className="counter--block">
<span>Counter</span>
<span>{count}</span>
</div>
<button onClick={increaseCounter}>Increase count</button>
</ErrorBoundary>
);
And also neither any of this will capture the error throw by
BuggyComponent
. To make this work we can do something like this:const App = () => {
return (
<div>
<h1>Counter Example</h1>
<ErrorBoundary>
<BuggyComponent />
</ErrorBoundary>
</div>
);
}
Now the
ErrorBoundary
will catch the error thrown by the BuggyComponent
since it is being wrapped by the error boundary.In the above usecase as you have seen whenever the count value reaches 5 it will throw a new error.
Note: The
if
block for this is placed in the rendering phase of the component because of which it creates a valid case for ErrorBoundary
to catch the error.const BuggyComponent = () => {
const [count, setCount] = React.useState(0);
const increaseCounter = () => {
setCount(preVal => preVal + 1);
}
if(count === 5) {
throw new Error("Crashing the app!!");
}
return (
<>
<div className="counter--block">
<span>Counter</span>
<span>{count}</span>
</div>
<button onClick={increaseCounter}>Increase count</button>
</>
);
}
const App = () => {
return (
<div>
<h1>Counter Example</h1>
<ErrorBoundary>
<BuggyComponent />
</ErrorBoundary>
</div>
);
}
But the same won't work if you place the
if
block inside the increaseCounter
function. The above example is altered to showcase this scenario:const BuggyComponent = () => {
const [count, setCount] = React.useState(0);
const increaseCounter = () => {
setCount(preVal => preVal + 1);
if(count === 5) {
throw new Error("Crashing the app!!");
}
}
return (
<>
<div className="counter--block">
<span>Counter</span>
<span>{count}</span>
</div>
<button onClick={increaseCounter}>Increase count</button>
</>
);
}
const App = () => {
return (
<div>
<h1>Counter Example</h1>
<ErrorBoundary>
<BuggyComponent />
</ErrorBoundary>
</div>
);
}
react-error-boundary
is a pretty impressive package. It solves most of the challenges faced by react's error boundary where it won't be able to catch errors such as errors thrown from event handlers, asynchornous code etc.You can refer to the package's github readme for more information.
Below is the implmentation of the above example but using
react-error-boundary
:import {ErrorBoundary} from 'react-error-boundary';
function ErrorFallback({error}) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre style={{color: 'red'}}>{error.message}</pre>
</div>
)
}
const BuggyCounter = () => {
const [count, setCount] = React.useState(0);
const handleIncrement = () => {
setCount(preVal => preVal + 1);
}
if(count === 5){
throw new Error("New Crashing Seq. Initiated");
}
return(
<div className="counter--block">
<span>Count</span>
<span>{count}</span>
<button onClick={handleIncrement}>Increment count</button>
</div>
);
}
const App = () => {
return(
<>
<h1>Counter Example</h1>
<ErrorBoundary FallbackComponent={ErrorFallback}>
<BuggyCounter />
</ErrorBoundary>
</>
)
}
ReactDOM.render(
<App/>,
document.getElementById("root")
);
Error logging is a crucial part of any application development process. It helps us to analyze and organize errors which are not catched during the testing process of the application. These error logging tools can generally be used to moniter the errors which are thrown on the client's machine/browser.
When it comes to error logging I find sentry.io to be a bliss. It has pretty impressive documentation and has wide range of support on different tech stacks such as Java, JS, React, React-Native etc.
Below is the modified example of the above example.
import React from "react";
import ReactDOM from "react-dom";
import * as Sentry from "@sentry/react";
import App from "./App";
Sentry.init({ dsn: "https://examplePublicKey@o0.ingest.sentry.io/0" });
const BuggyCounter = () => {
const [counter, setCounter] = useState(0);
return (
<>
<div className="counter--value">
{counter}
</div>
<div>
<button
className="counter--button"
onClick={() => { throw new Error("New Test Error")}}>
increment count
</button>
</div>
</>
)
}
const App = () => {
return (
<Sentry.ErrorBoundary fallback={"An error has occurred"}>
<BuggyCounter />
</Sentry.ErrorBoundary>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
// Can also use with React Concurrent Mode
// ReactDOM.createRoot(document.getElementById('root')).render(<App />);
In this example you need to first initialize the Sentry's instance with init function:
Sentry.init({ dsn: "https://examplePublicKey@o0.ingest.sentry.io/0" });
NOTE:
dsn
is data source name which tells the SDK where to send the events.Sentry also provides it's own error boundary component.
import * as Sentry from "@sentry/react";
const App = () => {
return (
<Sentry.ErrorBoundary fallback={"An error has occurred"}>
<BuggyCounter />
</Sentry.ErrorBoundary>
);
}
You can find the code used in this blogpost below:
Implementation of react error boundary from scratch:
https://codepen.io/keyurparalkar/pen/LYWJKvm?editors=0010
Implementation of react error boundary using react-error-boundary
package:
https://codepen.io/keyurparalkar/pen/bGqQNJe
Feel free to reach out to me @
33