No matter how hard you try, eventually your app code just isn’t going to behave the way you expect it to and you’ll need to handle those exceptions. If a render is thrown and unhandled, your application will be removed from the page, leaving the user with a blank screen... Kind of awkward...
Luckily for us, there’s a simple way to handle errors in your application using a special kind of component called an Error Boundary. Unfortunately, there is currently no way to create an Error Boundary component with a function and you have to use a class component instead, but we got another lucky break because there’s a terrific open source library we can use called
In this lesson, we’ll learn how to write our own simple error boundary and then how to use
react-error-boundary instead. We’ll also learn the implications of where we place our Error Boundaries in our React component tree.
Kent C. Dodds: [0:00] As hard as you work on your application, sometimes unexpected things will happen. I don't expect people will be throwing errors in the body of your function, but they could be calling functions that don't exist, which will [inaudible] results in an error. Let's take a look at how you can manage these with React.
[0:16] Here we have some explode state, where we initialize that to false. Then we have a button, where when you click on it, it changes that explode state to true. Then, we also render this <div>, where if there is some explode state, then we'll render that bomb, which will trigger this error.
[0:33] If we take a look at what things look like right now, we click on the bomb and our entire application goes away. We get this Uncaught Error: CABOOM in our console, and if we scroll down here a little bit, React logs out a component stack trace so we can track down which component threw this error.
[0:52] We have our App, and that is rendered right here, and then we have this <div>, which is rendered here, and then we have this <div>, which is rendered here, and then we have this bomb, which is rendered here, and that bomb is the thing that threw the error.
[1:05] It's giving us this tip. "Consider adding an error boundary to your tree to customize error handling behavior." That's just what we're going to do. Normally, I use the third-party library for this, but we're going to build our own little error boundary.
[1:19] Error boundaries have to be class components, and to create a class component with React, we're going to say class ErrorBoundary extends React.Component. In the body of our React class component, we're going to need a render method, and this is what's going to be rendered. It's basically the same thing as the body of our regular function components.
[1:42] Here, we're not going to render anything special with this ErrorBoundary. We're simply going to return this.props.children, so the React elements we return for this ErrorBoundary are going to be the same React elements that are provided to this ErrorBoundary as children. Let's go ahead and put those right here inside of this <div>.
[2:01] We'll say ErrorBoundary, paste in what we had before, and then close off that ErrorBoundary. If we save this, we get a refresh and when I click on this bomb, we get the exact same behavior that we had before. Now let's make this ErrorBoundary handle that error.
[2:17] The first thing that we're going to need is some state, so I'll say error: null, and then we'll have a static method called getDerivedStateFromError() that'll accept an error and then it'll return the state change that we want to make based on this error. We'll just return an object that has an error property and that's going to be assigned to the error that we're getting for this static method.
[2:40] When this happens, we're going to get a re-render. Let's grab that error from our state and then we can say if there's an error, then we'll return a <div> that says, "Oh no!" Let's save that and when we click this, we're going to see "Oh no!"
[3:00] We'll still see errors logged to the console for our benefit, but the application won't completely crash. React will simply re-render this ErrorBoundary with the error that was thrown and the ErrorBoundary gets to control what's being rendered.
[3:13] I'm going to go ahead and make a function component here called ErrorFallback(), which is going to be my generic fallback component for this application. We'll make this accept a prop called error. It will return a <div> that has a <p> tag with "Something went wrong:" and then a <pre> tag with the error.message.
[3:40] As the user of this error boundary component, I want to be able to provide to the error boundary the fallback component I want it to render when there's an error. I'm going to provide it the prop FallbackComponent. Here, I'll provide that component that I just created this ErrorFallback, paste that right in there.
[4:01] For our error boundary to accept and render that, we're going to have access to that fallback component on this.props. We can say this.props.fallbackComponent and we'll provide the prop error as error.
[4:18] You may recall that rendering custom components requires that the first character you use is capitalized, but the compiler also has a rule that if there are dots included in the component name, then those will also be treated as custom components, which is why this works.
[4:34] If we save this, we push the button and we're going to get, "Something went wrong. CABOOM." The app is still working, and we get all of the information logged to the console as well.
[4:45] As I said, I never use class components even for error boundaries because I prefer to use a third-party library, which I'm going to include now and that is called React ErrorBoundary. With the React Error Boundary UMD Export, it exposes a global variable called ReactErrorBoundary and it has a property on there called ErrorBoundary. I'm going to simply assign that to ErrorBoundary.
[5:11] You'll be pleased to know that I don't have to change any of my code for this to work. It will to continue to work because we basically built a simple version of the ErrorBoundary from this library. The library is doing a fair a bit more than what we're doing here, which is why I recommend you do that instead, but the API is exactly the same.
[5:30] One other thing I want to demonstrate here is that this ErrorBoundary can be rendered anywhere in the tree. The location of the ErrorBoundary has a special significance. The ErrorBoundary can handle any errors that are thrown by its descendants. It's also important to note that the ErrorBoundary is going to render something in place of all of its descendants when there is an error.
[5:50] That means that while we can see the bomb still right here and the rest of our application, if we were to move this ErrorBoundary up to encompass our entire app and save that, when we click this button, then the entire app is replaced by our ErrorFallback component.
[6:08] This may or may not be desirable based on a specific use case, but it could be useful to have one error boundary rendered up here and then other error boundaries rendered throughout your application with more specific ErrorFallbacks.
[6:22] Here we can have "Something went wrong there," and that error is going to be handled by the nearest ErrorBoundary here. Then if something went wrong somewhere else in our application, then this top-level ErrorBoundary could handle that error. This gives us some fine-grained control over what part of the tree is going to be replaced by what's rendered by our error boundary.
[6:43] Another important thing to note is that our error boundary can only handle certain errors, specifically errors that are happening within the React call stack. It won't handle errors that are happening in event handlers or if there's an error in an asynchronous callback, like a Promise handler. It will only handle errors that happen within a React call stack like the render method or a React useEffect callback.
[7:06] In the review, to handle React errors, you need to create an error boundary or, as I recommend, use react-error-boundary from npm. With this error boundary, you can wrap parts of your code. Any descendants of that error boundary will have its errors handled by the error boundary.
[7:23] With the error boundary from the react-error-boundary library, you can provide a FallbackComponent. That FallbackComponent will be rendered in the event of an error, allowing you to recover from the error or simply display an error message for the user to read.