Error Boundaries

Unfortunately, sometimes things go wrong and we need to handle errors when they do so we can show the user useful information. 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 an error is thrown and unhandled, your application will be removed from the page, leaving the user with a blank screen... Kind of awkward...
The problem is, you can't just wrap your entire application in a try/catch because of the way JavaScript works. If you recall from earlier lessons, when then you'll remember this:
const element = (
	<div>
		<h1>Calculator</h1>
		<Calculator left={1} operator="+" right={2} />
		<Calculator left={1} operator="-" right={2} />
		<Calculator left={1} operator="*" right={2} />
		<Calculator left={1} operator="/" right={2} />
	</div>
)

console.log(element)
// logs something like:

// {
// 	type: 'div',
// 	props: {
// 		children: [
// 			{ type: 'h1', props: { children: 'Calculator' } },
// 			{ type: Calculator, props: { left: 1, operator: '+', right: 2 } },
// 			{ type: Calculator, props: { left: 1, operator: '-', right: 2 } },
// 			{ type: Calculator, props: { left: 1, operator: '*', right: 2 } },
// 			{ type: Calculator, props: { left: 1, operator: '/', right: 2 } },
// 		],
// 	},
// }
You'll notice that the Calculator is just referenced, but not actually called at this stage. So if we tried to wrap our JSX in a try/catch, it would only catch errors during the creation of those elements, but the real errors will happen with React gets around to calling our component functions.
So you could wrap all of the contents of your components in a try/catch, but that's super annoying boilerplate:
// no, don't do this...
function Calculator({ left, operator, right }) {
	try {
		const result = operations[operator](left, right)
		return (
			<div>
				<code>
					{left} {operator} {right} = <output>{result}</output>
				</code>
			</div>
		)
	} catch (error) {
		return <div>Oh no! An error occurred!</div>
	}
}
// ugh, that would be terrible
If only there were something like:
<Try catch={<div>Oh no!</div>}>
	<App />
</Try>
Oh wait! There is! They're called Error Boundaries.
So you can make a special kind of component that implements the Error Boundary API, and then use it like so:
<ErrorBoundary fallback={<div>Oh no!</div>}>
	<App />
</ErrorBoundary>
And just like with try/catch you can place these error boundaries wherever you want to catch errors. You can even nest them to catch errors in different parts of your application to give your users a more meaningful experience.
Unfortunately, React doesn't ship this component as a built-in component, and there is currently no way to create an Error Boundary component as a modern function component so you have to use a class component instead.
Here's a simple version of an Error Boundary component:
class ErrorBoundary extends React.Component {
	state = { error: null }
	static getDerivedStateFromError(error) {
		return { error }
	}
	render() {
		return this.state.error ? this.props.fallback : this.props.children
	}
}
Then you'd use it like so:
<ErrorBoundary fallback={<div>Oh no!</div>}>
	<App />
</ErrorBoundary>
And now any errors thrown in App will be caught by the Error Boundary and render out the error message instead of crashing the app.
You're never going to need to do this though. Instead we'll follow the official docs' recommendation and use react-error-boundary which has a really nice API with a variety of ways to use it for different use cases. Check 📜 the docs for more details.

Async Errors

In React, you need to consider two types of errors: errors that happen during rendering, and errors that don't (like an event handler, a useEffect callback, or a promise .then/.catch).
Under the hood, React is using try/catch to catch errors that happen during rendering, but React cannot catch all of these kinds of errors because they happen outside of React's call stack.
So you need to surface those to React yourself. It's not very often you'll need to handle these kinds of errors in real world applications using modern React patterns and frameworks, but you may run into cases where it's useful. The easiest way to do this is to use the useErrorBoundary hook from react-error-boundary:
function App() {
	const { showBoundary } = useErrorBoundary()

	useEffect(() => {
		function handleMouseMove(event) {
			try {
				// Do something that could throw
			} catch (error) {
				showBoundary(error)
			}
		}
		window.addEventListener('mousemove', handleMouseMove)
		return () => {
			window.removeEventListener('mousemove', handleMouseMove)
		}
	})

	// Render ...
}
An alternative to using an error boundary for this type of error is to store the error in state and display that. Both approaches are valid, and which one you choose comes down to which feels better. I typically try both and choose the one I hate the least 😅

Localized Error Handling

Most applications will use more than a single error boundary. You should really think about error boundaries as try/catch blocks. You don't want to wrap your entire application in a single try/catch block because it's more difficult to handle errors in a meaningful way. Instead, you wrap things in more practical places so you can handle errors in a more localized way with the kind of context that makes sense for that part of the application.
You'll use error boundaries in a similar way. Just think about what the user will be able to do when errors occur and if you're able to give them more useful actions by handling the error in a more closely to where it originated, then do that.
For example, let's say you have a list/detail view. You might want to have an error boundary around the list and another around the detail view. This way, if there's an error in the list, you can show a message to the user and give them the option to retry fetching the list. If there's an error in the detail view, you can show an error and they can at least try other items in the list.
function App() {
	return (
		<div>
			<ErrorBoundary fallback={<div>Something went wrong with the list.</div>}>
				<List />
			</ErrorBoundary>
			<ErrorBoundary
				fallback={<div>Something went wrong with this item, try another.</div>}
			>
				<Detail />
			</ErrorBoundary>
		</div>
	)
}