Check out a free preview of the full Complete Intro to React, v8 course
The "Error Boundaries" Lesson is part of the full, Complete Intro to React, v8 course featured in this preview video. Here's what you'd learn in this lesson:
Brian demonstrates creating reusable error boundaries and using a feature called componentDidCatch to handle the caught errors. A component can only catch errors in its children and cannot catch its own errors.
Transcript from the "Error Boundaries" Lesson
[00:00:00]
>> We are gonna talk about special case React tools. So, we're gonna be starting here on error boundaries. So, regardless of whether or not you want to, sometimes you end up with errors inside of your components. Sometimes this is gonna come from things like malformed API responses that's been the one that's I've typically seen the most.
[00:00:25]
But usually it's things from like outside of your control maybe it's like user generated input, right? They're trying to put something in there that causes an error that crashes your app. You can be kind of defensive with that with something called an error boundary. A good example of a usefal error boundary as I'm building this entire app, like the entire course website that you see right now it's built in React, right?
[00:00:45]
It's built in Next.js, which uses React. Next.js, has like a error boundary at the top of your app so that it can catch your errors and say like, hey, there was an error inside of your app, here's how you can fix it. That's useful for like developer tooling and stuff like that as well.
[00:00:59]
So your app doesn't always need an error boundary but it's frequently helpful. So I alluded to in the previous lesson, that there are some things that function components that cannot do, the class components can do. And this is basically, mostly it which is a component did catch. It's a life cycle method that is only available to function components that you cannot do in class components.
[00:01:26]
So we're gonna go ahead and build one and then we're going to wrap I think our details route with an error boundary. So I'm gonna write this code here I'll be honest with you this is mostly taken directly from the React docs and it's not that I'm such a huge thing.
[00:01:46]
It's just that this is basically the only way to do an error boundary, right? So let's pop over to our app here and we're gonna create a new file in source, and we're gonna call it ErrorBoundary.jsx. And we're gonna import Component from React and we're gonna important Link from react router dom.
[00:02:19]
We're gonna make a class component you can call it ErrorBoundary and this extends component. So state, we're gonna have just one, we're gonna call it HasError true or false, what are we gonna put they're probably false, cuz it start with one. And you need to create a static method called getDerivedStateFromError.
[00:03:00]
Okay, so basically every time that there's an error, it's going to call this function and you're just gonna give it like, okay, if you encounter an error, this is what I want you to set the new state to be. So in our case, if there is an error, then we want the new state, to be has error true, right?
[00:03:18]
So you're just gonna return an object, it's basically like calling set state, right, hasError true. So anytime that there is an error, then you want it to have an error, right that tracks. Some of you might be wondering what static means, this is like a ES6 class thing this is not necessarily a React thing.
[00:03:40]
If you put a static method on a class, the way that you would call that method would be ErrorBoundary.GetDeriveState, right? So you would actually call the method directly on the class and not necessarily on an instance of the class right so you don't have like This is how you instantiate a class, right?
[00:04:03]
There's no method on here called getDerivedStateFromError. This one's not real because this is a static method. So the static method you call directly on the class itself, that doesn't make a tonne of sense, don't worry too much about it. But that's the difference between and that's why we have to have static here.
[00:04:20]
It's because React has to call it directly on the class and not necessarily on an instance of the class. But again, not super important only if you're curious. Okay, so we have our initial state, we have this get derived states from prop, and now we're gonna do a method here called ComponentDidCatch, which this is a life cycle method.
[00:04:44]
This accepts error and info so this is gonna be the actual error that it caught, right? And then this is gonna be some additional info that React will give you. I just did console.error ErrorBoundary component caught an error, whatever you think is gonna be a useful error message there with error and info.
[00:05:18]
I'm just doing this to console error. But, typically you probably would wanna send this to like a track js or a century or something like that something that's like some aggregation of error service. This would be a perfect place to like, hey, we're seeing client side errors here.
[00:05:33]
Ship that off to your error tracking software. So service kind of thing, right? Yeah, Mark.
>> You might have already covered this a bit, but why is this a class component since React 18 came out? Do we still not have anything for error boundaries and function components?
>> We do not, you still have to do it this way.
[00:05:55]
There's nothing in function components that allow you to do component did catch. These two methods here, are the reason why we have to use a class component cause there is no equivalent in function components. Yeah, good question. This is as far as I know, the only case where you still absolutely must use a class component.
[00:06:19]
There's other stylistic reasons to choose to do a class component, but this is when you have to use a class component. Okay, so I'll just leave you like a little note here. Typically, you would log this to something like track JS or New Relic or something like that.
[00:06:47]
Okay, and then here we'll put a render function again every component has a render function. You just gonna say, if this that's.state.hasError then we're gonna return some error state like UI. So I'm just gonna put this in an h2 here of there was an error with this listing.
[00:07:19]
We'll put a little link here, link to just the homepage, click here to, Go back to the home page, okay? So, if there's an error, display this, give them a link to go back to the homepage so it kind of gracefully fails for them. Otherwise we just want to return this.props.children right?
[00:07:51]
If there is no error we want this to just seamlessly pass through right? Because we're gonna surround the details route with an error boundary. And we want details to render just normally with no interference. If there is no error and if there is an error, then we wanted to show this, that's the idea.
[00:08:16]
Okay, and then we're going to export default ErrorBoundary and that is an ErrorBoundary. So let's go make details use this we're gonna go back to details.jsx we're going to import ErrorBoundary from ErrorBoundary. Now, the biggest temptation here would be to just wrap this with ErrorBoundary like this, right?
[00:08:49]
Don't do that, why? An error boundary can only catch things in components that are rendered inside of it. So if I put error boundary here I won't catch any error that happens in details, right? So if I have an issue up here with like is loading or the use query up here, it won't catch that because it's in the same component.
[00:09:14]
We have to have a component that wraps inside of so only catches, errors from components that are inside of it. So, I know this is a little weird, but the way we're gonna do this is we're gonna make a function here called details ErrorBoundary. And here we're just gonna return error boundary and we're gonna put details in inside of it.
[00:09:46]
And then we're gonna export errors about details boundary here. So does this expect any props? It doesn't. So our details page doesn't actually accept any props up here right? But what is one problem with this if for whatever reason we started passing in props to details as of right now they will get killed right here right?
[00:10:20]
So because we are not passing these props into details. So this would be like one of the only cases I'd be okay with you just saying ...props here using the spread operator here, because details error boundary doesn't care about these props at all. It's meant to be totally seamless and passed these props through seamlessly, right?
[00:10:46]
So this is what I would do here is basically say like I don't care what props are coming in just go directly through the ErrorBoundary and don't care about what's coming in, right? Details and restatement have an opinion of the property go into details. Does that make sense why we would do that?
[00:11:11]
Just to be super concrete. And don't follow me here, I'm just showing you an example I have details here let's say for example, I was passing through, I don't know const importantNumber equals 5. And here I put, importantNumber equals important Number, so now I'm passing in this property to details, right?
[00:11:37]
And I go back to details.jsx if I don't do this, important number will never make it to here, right, into these props. So I have to make sure that these props are being passed through directly to details, okay. That's concretely what I'm talking about when I say that.
[00:11:59]
For now, it actually doesn't matter, right, because we're not passing anything through. So if we were doing TypeScript which you will an intermediate React you do have to be more thoughtful about it Okay so this is now working but this is it for an ErrorBoundary, if we go back to details.jsx.
[00:12:23]
If there's any error inside of Details or any component rendered inside of Details like Carousel for example, this will get caught at this error boundary. So let's just see an example here, we have Carousel here at the beginning of render here, I'm just gonna say throw new Error.
[00:12:46]
The dreaded LOL error. So now if I go back to my AdoptMe page, That was just next JS rendering a bunch of times. As you can see here, there is an error with this listing, you should go back and so it doesn't crash, right? Now you can click here and it'll go back here.
[00:13:08]
So it's catching this error here, if you look at our console here you can see ErrorBoundary component caught an error. This is cuz it's calling the component did catch, right? All right this is the error message that we wrote here and it's getting the derive state from error.
[00:13:26]
So therefore, this is all working the way that we intended. Yeah, question.
>> And this error boundary is totally reusable like we could wrap pretty much any component with an expected?
>> Absolutely. So what I would typically do, we made a very not reasonable cuz it's displaying this message, right?
[00:13:44]
The way that you can handle this is you can basically make a prop here. So instead of returning this error message, you would return this .props.errorComponent. Right and now this is just gonna display that and if we go back to Details we would have to provide to our error boundary here error component equals blah right?
[00:14:18]
And then now this is super reusable right this error boundary could be used anywhere because you can put any error message you want in there right So, yes, very reusable. I'm very much, I think I've mentioned this previously in the class, but I'm very much the person who's like, let's wait until we repeat ourselves a couple times before we abstract, right?
[00:14:42]
So right now this is the only error boundary we have in our apps I'm totally fine just dumping directly in the message that I wanna put here. But if I start using this two three four times then I'll make it more reusable. But that is a huge temptation with React something that I see people stumble over all the time is they wanna make all these very elegant, reusable components for one use case, right?
[00:15:05]
So you're just adding complexity for really no use. So yeah, my invitation is to not do that were to repeat, I'm usually again, I say read it twice on the third time think about abstracting Okay, let's remove our error here. Okay, other questions? Anything about errors kind of useful?
[00:15:39]
I'll see a lot of people like wrap their entire app, right? So they'll come back to the app.jsx level, and then they'll just like put error boundary here. Makes sense like if you just want to like have a generalized hey, there was an error in the app, we're gonna send you back to the homepage kind of that error.
[00:15:58]
And I'll see people do like very fine granular ones, it just depends on what your apps use cases. Yeah, Mark.
>> So where would you wrap specific component versus like higher up the tree and the errors.
>> That's going to be very much to taste. So my personal opinion on it is If I had a component where I expected there to be errors, and like a really good example that, would be like I used to work on reddit.com, right?
[00:16:35]
And I used to work on like the Reddit redesign. So, we had the like authoring, what you see what you get kind of editor, right? Those things just have errors because it's user input, user input is just rife with errors, right? So that'd be a really good use case to like gracefully handle errors and coming from a user.
[00:16:55]
That would be a very specific use case of like, okay, this is user input, this is going to be right for errors. This is a really good place for me to try and be very intentional. About trying to catch an error versus, I don't know, something like my site here, right?
[00:17:14]
There's not really gonna be much errors on a pretty static course website, right? So in this particular case, I don't have an error boundary on this, by the way. But if I wanted to have one, then I would probably just have one top level one. I mean, it's basically the same logic that goes through your head as like, when am I using throw catch?
[00:17:37]
No or rather a try catch, not throw catch, you don't use try catch everywhere, right? You use it in specific cases when you think there might be an error. You can think of error boundaries as basically the catch of the React world. Because it literally is. [LAUGH]
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops