- Published on
How to Handle Your Web Components Errors Effectively
- Authors
- Name
- Ismail Tlemcani
- @Ismailtlem
Hello readerđ
In this blog post, we will give an overview on why catching and handling errors is important in software engineering in general and the ways offered by popular web development tools such as React to effectively handle errors.
Why errors happen ?
The majority of the well known applications we use on a daily basis, like Facebook, YouTube, etc.,
are extremely huge, sophisticated and very often made by several people (can be more than 100 engineers for very big products). Very often, despite having many automated systems that are checking for errors before they reach production, there are very often errors in the code of those products that makes it crash for some specific situations. It's not that the developers who created those products are stupid, lazy or incompetent, it's just that, in the rush to meet a deadline, itâs difficult to foresee everything a user could do to the system they are working on which often leads to the software crashing in some use cases.
Because of the importance of software programs in our current world and the criticality of the areas in which some programs are used such as the healthcare industry, it is important to effectively handle potential errors that might be in the code of those programs in order to offer the best possible product to the end users and to avoid any bad consequence of a faulty program.
Why is error handling important in React apps
It is important to handle potential errors that can be in your React app because those errors might end-up crashing the entire React lifecycle or reaching the top-level of the main execution thread. From React 16 onward, as stated in the official documentation, errors that are not caught will result in unmounting of the whole React component tree.
Because of the importance of handling errors we stated above, it is very important to handle potential issues that may happen, and eventually provide suitable feedback to the users and also to the developers so that those errors are quickly fixed.
Fortunately, implementing such patterns is simple with the recent React APIs. More specifically, React 16 introduces the Error Boundaries mechanism that is very useful for that purpose.
Component stack trace
From React 16, all errors that occurred during the rendering phase are printed into the console in development. In addition to that, the component stack trace is also provided. We can therefore see where exactly in the component tree the error has happened:
The component stack trace additionally includes filenames and line numbers that you can use to see where exactly the error happened. The component stack trace works by default in projects created using Create React App. If you donât use it, you can manually add this plugin to your Babel setup.
How to effectively handle errors in React components ?
Catching error with try ⌠catch
If you are familiar with JavaScript, you are probably familiar with the usual try ⌠catch block used to catch errors.
Using the usual try...catch statement is not effective to catch errors in your react components because it only works for imperative code and not the declarative code we write in JSX.
Moreover, with a try...catch statement, we might cause the entire react app to break instead of just the faulty component which is not what you most probably want.
Error boundaries
Error Boundaries are the recommended way to handle potential errors in React components. According to the React documentation, âError boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.â
Not all errors will be catched with Error boundaries. Below is a list of errors that wonât be catched using this mechanism:
- Event handlers
- Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
- Server side rendering
- Errors thrown in the error boundary component itself. Errors Boundaries only catch errors in the components below them in the tree
Why Errors Boundaries are useful
When using Error Boundaries, errors that might happen in a specific component wonât make the global component tree to crash. Instead, the error propagation will stop at the Error Boundary level
How to implement Error boundaries
Error Boundaries are implemented using class components. According to the React documentation, a class component becomes an error boundary if it defines either (or both) one of the lifecycle methods static getDerivedStateFromError or componentDidCatch.
- static getDerivedStateFromError(error)Â is used to render a visual fallback UI after an error has been thrown. It will be called after a descendant component throws an error. This method takes the error as an argument and the value returned by this function is used to update the state.
- componentDidCatch(error, info)Â is usually used to log error information. It will be called as soon as an error reaches our component. This method accepts two arguments:
- error The error that was thrown
- info An object storing the componentStack trace showing which component has thrown this error.
Simple implementation of error boundaries
As we previously mentioned, an error boundary is just a class component that implements one of those methods static getDerivedStateFromError() or componentDidCatch() or both.
Real example
Here is a simple React app where we are explicitly throwing an error in the Users component below
import { Users } from './Users';
function App() {
return (
<div className="App">
<h1>Hello dear reader</h1>
<Users />
</div>
);
}
export default App;
Here is the Users component where we are explicitly throwing an error
export const Users = () => {
throw new Error('Error!');
return <div> Users </div>;
};
Running this React app will throw the following error and cause the entire app to crash
To prevent the app from entirely crashing, we need to add the Error Boundary component. Here is a simple implementation of an error boundary class
import React from 'react';
export class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
After defining this, we only need to wrap the necessary component where we might have an error inside the Error Boundary component.
import { ErrorBoundary } from "./ErrorBoundary";
function App() {
return (
<div className="App">
<h1>Hello</h1>
<ErrorBoundary>
<Users />
<ErrorBoundary />
</div>
);
}
export default App;
The app now runs but the error propagation stops at the Error Boundary component and the app doesnât entirely crash.
Advanced error handling experience : react-boundary-library
If you are not satisfied with the features offered by the Error Boundary component, you can use the react-boundary-library that provides a retry mechanism as well as a way to render a fallback component in case of an error.
- You can install this library simply by
# if you use npm
npm install --save react-boundary-library
# if you use yarn
yarn add react-boundary-library
How to use react-boundary-library
- Firstly, all the features offered by this library are well explained in their official repository. Weâll see here a simple example to help you get started.
- To handle errors using this library, you can do it simply by wrapping the component where your error might show up inside the ErrorBoundary component from react-boundary-library. You can also provide a fallback component as a prop to the Error Boundary component which will be the UI shown in case of an error. Here is a concrete example
import { ErrorBoundary } from 'react-error-boundary';
import { Users } from './Users';
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>Failed to load users:</p>
<p>{error.message}</p>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
function App() {
return (
<div className="App">
<h1>Hello dear reader</h1>
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Users />
</ErrorBoundary>
</div>
);
}
- The Users component is the same one as before.
- The Error Boundary component takes one mandatory prop which is the fallback component or JSX code to display in the case of an error.
- The fallback component accepts error and resetErrorBoundary as props. The resetErrorBoundary prop will reset the error boundary's state when called, and can be useful for a "try again" button. error is the thrown error. Here is the output we get when running the above code
Final words
Generally speaking, a software error is essentially a mismatch between what is expected from the program and the real output. It is almost impossible to make software that is error-free, and that even with the development of automated testing tools. On some occasions, these errors have little effect, while in other contexts, like in the healthcare industry or in banking applications, the effects of an error can be extremely expensive to fix. It is therefore important to handle any potential error in the most efficient way possible.
In the case of web applications and more specifically in the case of web components, modern JavaScript frameworks usually offer ways to handle errors that might be in your web component other than just the usual try ⌠catch. Error Boundaries components are an effective solution offered in the React ecosystem for that purpose. This solution helps in offering visual UI feedback and stops the error propagation in order to not cause the entire app to crash. There are as well other libraries that one can use to provide an advanced error-handling experience such as react-boundary-library in the React ecosystem.