If you already have a subscription, you can sign in.
Enjoy free content straight from your inbox 💌
00:00
In this session we will look at the problem that React Error Boundaries are designed to solve, along with some use cases for which you actually do not need them, but if you do need them, we will also look at how to create and use them effectively. So let's go. Before we can talk about errors, we would need to create some. So let's look at a simple application that will demonstrate some of the kinds of errors that can occur in a React app. Here we have a simple counter component that takes account and increment function within the ui. It displays the count in a simple button wired to that increment function within our app.
00:32
To use this counter component, we create a simple state variable that will maintain the count and increment function, which will increment the count and then we pass the count and the increment s props to our counter component. The UI behaves exactly how you would expect it displays the count along with the button to increment its value. Now before we look at error boundaries, let's get clear about how standard error handling works within JavaScript and for which use cases you do not need a React specific error boundary. Let's look at a poorly written function which for whatever reason decides that when it's passed the value
01:05
to just straight up throws an error. Now if we have some bad code invoked from a click handler within our application, interestingly this is not something that will cause the application to crash and we can actually verify this by heading over to the browser and interacting with our application. Now even though at this particular point in time that bad code has started to throw an error, which means that we are no longer able to increment the counter, however, we do not see the application like straight up crashing. We can verify that there is an error happening when we open up the console.
01:37
This is because code that executes outside of React render doesn't necessarily cause our React application to crash. Browsers are quite resilient with bad event handlers and they are easy to isolate for the browser without causing issues in other portions of our app. Now if we did want to handle an issue like this, we would do it the same way you would do in any other piece of transcript, which is wrap it up in a try and catch. And with this simple error handling in place, we no longer get an error and we can continue to increment beyond the value of two. So question that you should have is
02:10
that if we can handle errors with a simple dry catch, then why does React need to create its own concept of an error boundary? Now, something that I said might have caught your attention and that is when an error is outside of a React render, it doesn't cause issues. But what if we move this error into the rendering of the counter component and now when the counter component is getting rendered, it might throw an error. So can you think of a way that we could put a try-catch? When we are rendering the counter component, unfortunately we are not invoking the counter component. We are simply telling React that, hey, when you decide
02:45
to render the app, make sure that you also invoke and then render the result of the counter component. So a try-catch over here would not actually get invoked because counter is not going to get invoked right now what is worse Is that when React will eventually try to render the counter, then the error will get thrown and it has to essentially be handled by React. Now if silly stuff like this does end up happening in our component, and let's be honest, we all write silly code, what React does by default is actually just unmark the app because it cannot read our minds to figure out
03:19
what it should render when the components themselves refuse to do so. However, it does print to the console the fact that we should be using an error boundary if we want to handle this case gracefully. There are a few things that we need to know about creating our own error boundary components. They need to be a class component and second, they need to provide a static get derived state from error lifecycle method. So let's create a first error boundary. We will create it in a file called error boundary tsx. You are free to call the file whatever you want. You are free to call the component whatever you want.
03:52
Here we are creating a component called error boundary. And the way you create a class component is that it must extend react component. And hey, we are specifying what props this particular component takes and the error boundary will by default take the children that get passed in and then we provide a render method within this class, which will essentially be whatever we want this particular component to render. Hey, we are simply rendering this props children. Now as we've seen in our previous custom components, this is essentially the same as a function component
04:25
where we take props that contain children and simply render out props children. So as you can imagine, we normally prefer to use function components within application because they're much neater. However, for error boundaries you must use a class component. The reason for this will become obvious when we look at the lifecycle method. Now let's define some simple state that we want for our class component and we will have a simple piece of state which is has error, which will be a bullion. Now initially of course before any rendering there are no errors that would've happened.
04:57
So state will be initialized to has error false. And now because we are using a class component, we get this static get derived state from error method which gets passed any error that might happen while rendering the children. And from this error we should return the derived state that we want the component to transition to. For our simple error boundary, we are simply saying, yeah, there's an error lifecycle methods are a feature that are only supported by class components and therefore the error boundary must be a class component. Now, once we have this updated state returned,
05:30
we can actually read its value when we are rendering the component. And if the state tells us that yes, there is an error, then we will render a fallback ui. Our simple UI consists of a simple D, something went wrong. Now that's a very simple error boundary component To use it, we jump into our application, import this component and then pass the counter component as children for the error boundary. Now, when React encounters an error while rendering the counter component, it'll invoke the get derived state within error boundary and the error boundary component
06:02
of course will then transition into the error UI and we can verify that by jumping into the browser incrementing the counter to it starts to error. And then we see this fallback error boundary ui. Now the error boundary is a component which you control, which means that you can expand it with as many belts and whistles as you need. For example, in addition to taking the children as props, we could also request a fallback UI as a prop and then instead of just rendering the D something went wrong, we could render this top props not fallback.
06:35
This allows the consumers of the error boundary to provide a fallback ui. So as an example, when we are rendering the counter component and it crashes, we can render a UI that explicitly mentions the fact that the counter crashed. And now instead of seeing something general like something went wrong, we see this improved specific error message. One more thing to note is that it's called an error boundary and not a global error handler because you get to decide how many times and at what levels you want to handle the errors. As an example, consider an application we'll be maintain the
07:07
state for two counters, and then within the UI we render both the counter components within a single error boundary. And for the fallback UI we provide a diviv counters crashed. Now if we jump to the application and if either of the two counters crashes because they are wrapped by that error boundary, that error boundary will display that the counters have crashed. Just like a try and catch, we can actually choose to wrap the individual counters with their own error boundaries, with their own fallback UIs so that when one counter crashes it has no impact on the
07:41
other counters ui. And we can verify this behavior by jumping back to the app causing one of the counters to crash and only the crash counter gets the fallback ui. One thing to note is that the get derived straight from error lifecycle method is actually called wild reactors rendering the ui. So we shouldn't put side effects in there. For example, we shouldn't be firing any events, but fortunately there is another lifecycle method for this purpose. So we jump into our error boundary component and add this additional lifecycle method called component did catch just like get derived state from error.
08:15
It does get past in the error, but it gets past another thing called the error info. The JavaScript error object actually contains the JavaScript call stack, but this error info has this thing called Component Stack which actually returns the component hierarchy to see the difference. Let's jump into the UI and trigger a new error so that we can see it on the console. You can see that the call stack on the error object logs how the function was executed from a JavaScript perspective. And of course that's sort of starts from React eventually hitting our counter component, whereas component stack looks at it from a
08:49
component perspective that the app component rendered a div that rendered the error boundary, which eventually Rendered the failing counter component. Now to recap for an error boundary, we have a class component with two lifecycle methods, a static get drive state from error where we should only return the updated state without any additional side effects. And then a component date catch lifecycle method where you can decide to send the error information to some monitoring service. I'll wrap things up there. As always, thank you for joining me and I'll see you in the next one.