Whenever an error occurs and an exception is thrown in a React application, there is a strong possibility that the application display no longer works and that the user will only see a blank page. To avoid this behavior, React introduced so-called Error Boundaries in version 16.0.
An Error Boundary describes a component which can catch certain errors in its children and can also render an alternative component tree to protect users from experiencing a blank page. Error Boundaries always serve as a parent component of a component tree. If an exception is thrown in the component tree, the Error Boundary can intercept and handle the error. Try and think of error boundaries as a special form of a try / catch
block for component hierarchies.
They can deal with mistakes that result from the handling of the following situations:
-
Errors in lifecycle methods
-
Errors in the
render()
method anywhere inside the Error Boundary -
Errors in the
constructor()
of a component
If React encounters an error in a lifecycle method, the render()
method or in the constructor of a component, the Error Boundary can safely prevent it. It can display a fallback that can prompt the user to restart their application or inform them that something has gone wrong. Similar to Context components, Error Boundaries can be nested inside each other. If an error occurs, the implementation of the higher Error Boundary component takes precedence.
Attention: Error Boundaries‘ primary goal is to prevent and deal with errors in the handling of user interfaces which would otherwise prevent further rendering of the application status. If you think about implementing form validation with Error Boundaries, please refrain from doing so as Error Boundaries were not intended for this use case and should not be used for that matter.
There are certain situations in which Error Boundaries do not work:
-
in asynchronous code (like
setTimeOut()
orrequestAnimationFrame()
) -
in server-side rendered components (SSR)
-
in errors which occur in the Error Boundary itself
Error Boundaries will not work in these situations as it is either not necessary or not possible for them to deal with the problem at hand. If an event-handler throws an error, this might not necessarily impact its render and React can continue to show a working interface to the user. The only repercussion would be the missing interaction based on said event.
Implementing an Error Boundary
Implementing an Error Boundary
There are two simple rules when it comes to implementing an Error Boundary:
-
1.
Only Class components can be turned into an Error Boundary
-
2.
The class has to implement the static
getDerivedStateFromError()
method or the class methodcomponentDidCatch()
(or both of them)
Strictly speaking, we are already dealing with an Error Boundary from a technical point of view if one or both of the methods mentioned above have been implemented. All other rules that apply to regular Class components also apply to Error Boundaries.
Let’s look at an implementation of an Error Boundary:
class ErrorBoundary extends React.Component {
static getDerivedStateFromError(error) {
componentDidCatch(error, info) {
console.log(error, info);
if (this.state.hasError) {
return <h1>An error has occured.</h1>;
return this.props.children;
First of all, we define a new component. We have named this component ErrorBoundary
but it is possible to give it any other name too. You can freely choose the name of the Error Boundary and only need to adhere to React’s component naming conventions: components need to start with a capital letter and be a valid JavaScript function name.
For matters of simplicity and readability, I would urge you to choose clear and identifiable component names such asAppErrorBoundary
or DataTableErrorFallback
. This will allow other team members in your project to see which components are used to deal with errors at a glance.
In the above example we have set up state with a property of hasError
and provided an initial value of false
as errors usually do not occur during initialization.
Next, let’s look at the static getDerivedStateFromError()
method. Using this method, React is informed that the component in use is supposed to act as an Error Boundary and should come into effect if an error occurs in its children. The method itself is passed an error
object which is the same as the object which is also passed to the catch
block of the try / catch
statement.
getDerivedStateFromError()
works very similar to the getDerivedStateFromProps()
method we have already encountered in the chapter on lifecycle methods. It can return a new object and thus create new state or leave all as is by returning null
. In the above example, we have set the hasError
property to true
and also save the error
object in our state. As the method itself is static though, it cannot access other methods in the component.
This method is called during the render()
phase of a component when React compares the current component tree with its previous version and just before the changes are committed to the DOM.
The componentDidCatch()
method has also been implemented. It receives an error object as its first parameter and React-specific information as its second. This information contains the «Component Stack» — crucial information which allows us to trace in which components we have encountered errors and more specifically how which children and children of children were involved. It will display the component tree up until an error will occur. If you want to use an external service to log these errors, this method is a good place to deal with side effects. componentDidCatch()
is run during the Commit phase meaning just after React has displayed changes from state in the DOM.
As componentDidCatch()
is not a static method, it would be entirely possible to modify its state via this.setState()
. However, the React Team plans to prohibit this usage in the future which is why I do not recommend it at this point. It is safer to use the static getDerivedStateFromError()
method instead to create a new state and react to errors once they have occurred.
Finally, we react to possible errors in the render()
method. If the hasError
property in state is set to true
, we know that an error has occurred and can thus display a warning such as <h1>An error occured.</h1>
. If on the other hand everything works as expected, we simply return this.props.children
. How exactly the errors encountered are dealt with is up to the developer. For example, it might be sufficient to inform the user that certain information cannot be displayed at the moment if the error is only small. If however serious errors have been encountered, we should prompt the user to reload the application.
Error Boundaries in practice
Error Boundaries in practice
We now know how to implement an Error Boundary: by adding either static getDerivedStateFromError()
or componentDidCatch()
to your components. Error Boundaries should not implement their own logic, should not be too tightly coupled to other components and be as independent as possible. It is at the developer’s discretion to decide how granular the Error Boundary should be according to the specific use case.
It’s a good idea to implement different and nested Error Boundaries to cater to a variety of errors: one Error Boundary that wraps around the whole application, as well as one that wraps only optional components in the component tree. Let’s look at another example:
import React from ‘react’;
import ReactDOM from ‘react-dom’;
<ServiceUnavailableBoundary>
</ServiceUnavailableBoundary>
ReactDOM.render(<App />, document.querySelector(‘#root’));
Two Error Boundaries are used in the above example: ErrorBoundary
and ServiceUnavailableBoundary
. While the outer boundary will catch errors that might occur in the ApplicationLogic
component, the ServiceUnavailableBoundary
could catch errors in the weather widget and display a more granular error message like «the service requested cannot be reached at the moment. Please try again later».
If the WeatherWidget
component throws an error, the ServiceUnavailableBoundary
will catch it and everything that is currently used in the ApplicationLogic
component will remain intact. If we did not include the WeatherWidget
in its own Error Boundary, the outer Error Boundary would be used instead and the ApplicationLogic
component would not be shown.
Generally, it is good practice to have at least one Error Boundary as high up as possible in the component hierarchy. This will catch most unexpected errors like a 500 Internal Server Error
page would do and can also log them. If needed, further Error Boundaries should be added to encompass useful logic in further component trees. This depends entirely on how error prone a specific area of the tree is (due to unknown or changing data) or if a specific area of the tree has been neglected.
Since React version 16, components will be «unmounted» and removed from the tree if a serious error occurred or an exception was thrown. This is important as it ensures that the user interface does not suddenly stop working or returns incorrect data. It is especially critical to ensure if we were to work with online banking data. Imagine the consequences if we were to incorrectly send money to the wrong recipient or transfer an incorrect amount.
In order to deal with these errors and risks properly, Error Boundaries were introduced. They allow developers to inform users that the application is currently in an erroneous state. As errors and mistakes can never be fully avoided in an application, using Error Boundaries is highly recommended.
Abi Noda
Posted on Jun 27, 2018
• Updated on Jul 3, 2018
If you like this article please support me by checking out Pull Reminders, a Slack bot that sends your team automatic reminders for GitHub pull requests.
One challenge I recently ran into while working with GraphQL and React was how to handle errors. As developers, we’ve likely implemented default 500, 404, and 403 pages in server-rendered applications before, but figuring out how to do this with React and GraphQL is tricky.
In this post, I’ll talk about how our team approached this problem, the final solution we implemented, and interesting lessons from the GraphQL spec.
Background
The project I was working on was a fairly typical CRUD app built in React using GraphQL, Apollo Client, and Express GraphQL. We wanted to handle certain types of errors — for example, the server being down — by displaying a standard error page to the user.
Our initial challenge was figuring out the best way to communicate errors to the client. GraphQL doesn’t use HTTP status codes like 500, 400, and 403. Instead, responses contain an errors
array with a list of things that went wrong (read more about errors
in the GraphQL spec).
For example, here’s what our GraphQL response looked like when something broke on the server:
{
"errors": [
{
"message": "TypeError: Cannot read property 'name' of undefined",
"locations": [
{
"line": 2,
"column": 2
}
],
"path": [
"program"
]
}
],
"data": {
"program": null
}
}
Enter fullscreen mode
Exit fullscreen mode
Since GraphQL error responses return HTTP status code 200, the only way to identify the kind of error was to inspect the errors array. This seemed like a poor approach because the error message
was the message from the exception thrown on the server. The GraphQL spec states that the value of message is intended for developers, but it does not specify whether the value should be a human-readable message or something designed to be programmatically handled:
Every error must contain an entry with the key message with a string description of the error intended for the developer as a guide to understand and correct the error.
Adding Error Codes to GraphQL Responses
To solve for this, we added standardized error codes to our error objects, which could be used by clients to programmatically identify errors. This was inspired by how Stripe’s REST API returns string error codes in addition to human-readable messages.
We decided on three error codes to start: authentication_error
, resource_not_found
, and server_error
.
To add these to our GraphQL responses, we passed our own formatError
function to graphql-express that maps exceptions thrown on the server to standard codes which get added to the response. The GraphQL spec generally discourages adding properties to error objects, but does allow for it by nesting those entries in an extensions
object.
const formatError = (error) => {
const { constructor } = error.originalError;
let code;
switch (constructor) {
case AuthorizationError:
code = 'authorization_error';
case ResourceNotFound:
code = 'resource_not_found';
default:
code = 'server_error';
}
return {
extensions: {
code
},
...error
};
};
app.use('/graphql', (req, res) => graphqlHTTP({
schema,
graphiql: config.graphiql,
context: { req, res },
formatError
})(req, res));
Enter fullscreen mode
Exit fullscreen mode
Our GraphQL response errors were then easy to classify:
{
"errors": [
{
"message": "TypeError: Cannot read property 'name' of undefined",
"locations": [
{
"line": 2,
"column": 2
}
],
"path": [
"program"
],
"extensions": {
"code": "server_error"
}
}
],
"data": {
"program": null
}
}
Enter fullscreen mode
Exit fullscreen mode
While we developed our own way of adding codes to responses generated by express-graphql, apollo-server appears to offer similar built-in behavior.
Rendering error pages with React Error Boundaries
Once we figured out a good way of handling errors in our server, we turned our attention to the client.
By default, we wanted our app to display a global error page (for example, a page with the message “oops something went wrong”) whenever we encountered a server_error
, authorization_error
, or authorization_not_found
. However, we also wanted the flexibility to be able to handle an error in a specific component if we wanted to.
For example, if a user was typing something into a search bar and something went wrong, we wanted to display an error message in-context, rather than flash over to an error page.
To achieve this, we first created a component called GraphqlErrorHandler
that would sit between apollo-client’s Query
and Mutation
components and their children to be rendered out. This component checked for error codes in the response threw an exception if it identified a code we cared about.
import React from 'react';
import {
ServerError,
AuthorizationError,
ResourceNotFound
} from '../errors';
const checkFor = (code, errors) => errors && errors.find( e => e.extensions.code === code);
const checkError = ({ networkError, graphQLErrors }) => {
// networkError is defined when the response is not a valid GraphQL response, e.g. the server is completely down
if ( networkError ) {
throw new ServerError();
}
if (checkFor('server_error', graphQLErrors)) {
throw new ServerError();
}
if (checkFor('authorization_error', graphQLErrors)) {
throw new AuthorizationError();
}
if (checkFor('resource_not_found', graphQLErrors)) {
throw new ResourceNotFound();
}
};
const GraphqlErrorHandler = ({ error, children }) => {
if (error) checkError(error);
return children;
};
export default GraphqlErrorHandler;
Enter fullscreen mode
Exit fullscreen mode
To use the GraphqlErrorHandler
, we wrapped apollo-client’s Query
and Mutation
components:
import React from 'react';
import Query from 'Components/graphql/Query';
import GET_PROGRAM from './queries/getProgram';
import ViewProgram from './ViewProgram';
const ViewProgramContainer = (props) => {
const { programCode } = props.match.params;
return (
<Query query={GET_PROGRAM} variables={{ programCode }}>
{({ loading, data, }) => (
<ViewProgram program={data.program} loading={loading} />
)}
</Query>
);
};
export default ViewProgramContainer;
Enter fullscreen mode
Exit fullscreen mode
Now that our React app was throwing exceptions when the server returned errors, we wanted to handle these exceptions and map them to the appropriate behavior.
Remember from earlier that our goal was to default to displaying global error pages (for example, a page with the message “oops something went wrong”), but still have the flexibility to handle an error locally within any component if we desired.
React error boundaries provide a fantastic way of doing this. Error boundaries are React components that can catch JavaScript errors anywhere in their child component tree so you can handle them with custom behavior.
We created an error boundary called GraphqlErrorBoundary
that would catch any server-related exceptions and display the appropriate error page:
import React from 'react';
import ServerErrorPage from 'Components/errors/ServerError';
import NotFoundPageErrorPage from 'Components/errors/NotFound';
import UnauthorizedErrorPage from 'Components/errors/Unauthorized';
import {
ServerError,
AbsenceError,
AuthorizationError,
ResourceNotFound
} from '../errors';
class GraphqlErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null
};
}
componentDidCatch(error) {
if ( error.name === AuthorizationError.name ) {
this.setState({ error: AuthorizationError.name });
} else if ( error.name === ServerError.name ) {
this.setState({ error: ServerError.name });
} else if ( error.name === ResourceNotFound.name ) {
this.setState({ error: ResourceNotFound.name });
} else {
this.setState({ error: ServerError.name });
}
}
render() {
if (this.state.error === ServerError.name ) {
return <ServerErrorPage />
} else if (this.state.error === AuthorizationError.name) {
return <UnauthorizedErrorPage />
} else if (this.state.error === ResourceNotFound.name) {
return <NotFoundErrorPage />
}
return this.props.children;
}
}
export default GraphqlErrorBoundary;
Enter fullscreen mode
Exit fullscreen mode
We then wrapped our app’s components with this error boundary:
const App = () => {
return (
<div className='appContainer'>
<Header />
<GraphqlErrorBoundary>
<Routes />
</GraphqlErrorBoundary>
<Footer />
</div>
);
};
Enter fullscreen mode
Exit fullscreen mode
If we wanted to handle errors within a specific component instead of rendering an error page, we could turn that component into an error boundary. For example, here’s what it’d look if we wanted custom error handling behavior in our component from earlier:
import React from 'react';
import Query from 'Components/graphql/Query';
import GET_PROGRAM from './queries/getProgram';
import ViewProgram from './ViewProgram';
class ViewProgramContainer extends React.Component {
componentDidCatch(error) {
if (error.name === ServerError.name) {
// do something
}
}
render() {
const { programCode } = this.props.match.params;
return (
<Query query={GET_PROGRAM} variables={{ programCode }}>
{({ loading, data, }) => (
<ViewProgram program={data.program} loading={loading} />
)}
</Query>
);
}
}
export default ViewProgramContainer;
Enter fullscreen mode
Exit fullscreen mode
Wrap up
GraphQL is still relatively new, and error handling is a common challenge that developers seem to be running into. By using standardized error codes in our GraphQL responses, we can communicate errors to clients in a useful and intuitive way. In our React apps, error boundaries provide a great way to standardize our app’s error handling behavior while still having flexibility when we need it.
by Abi Noda
If you like this article please support me by checking out Pull Reminders, a Slack bot that sends your team automatic reminders for GitHub pull requests.
One challenge I recently ran into while working with GraphQL and React was how to handle errors. As developers, we’ve likely implemented default 500, 404, and 403 pages in server-rendered applications before, but figuring out how to do this with React and GraphQL is tricky.
In this post, I’ll talk about how our team approached this problem, the final solution we implemented, and interesting lessons from the GraphQL spec.
Background
The project I was working on was a fairly typical CRUD app built in React using GraphQL, Apollo Client, and express-graphQL. We wanted to handle certain types of errors — for example, the server being down — by displaying a standard error page to the user.
Our initial challenge was figuring out the best way to communicate errors to the client. GraphQL doesn’t use HTTP status codes like 500, 400, and 403. Instead, responses contain an errors
array with a list of things that went wrong (read more about errors
in the GraphQL spec).
For example, here’s what our GraphQL response looked like when something broke on the server:
Since GraphQL error responses return HTTP status code 200, the only way to identify the kind of error was to inspect the errors
array. This seemed like a poor approach because the error message
property contained the exception thrown on the server. The GraphQL spec states that the value of message
is intended for developers, but it does not specify whether the value should be a human-readable message or something designed to be programmatically handled:
Every error must contain an entry with the key message with a string description of the error intended for the developer as a guide to understand and correct the error.
Adding Error Codes to GraphQL Responses
To solve for this, we added standardized error codes to our error objects, which could be used by clients to programmatically identify errors. This was inspired by how Stripe’s REST API returns string error codes in addition to human-readable messages.
We decided on three error codes to start: authentication_error
, resource_not_found
, and server_error
.
To add these to our GraphQL responses, we passed our own formatError
function to express-graphql that maps exceptions thrown on the server to standard codes which get added to the response. The GraphQL spec generally discourages adding properties to error objects, but does allow for it by nesting those entries in a extensions
object.
Our GraphQL response errors were then easy to classify:
While we developed our own way of adding codes to responses generated by express-graphql, apollo-server appears to offer similar built-in behavior.
Rendering error pages with React Error Boundaries
Once we figured out a good way of handling errors in our server, we turned our attention to the client.
By default, we wanted our app to display a global error page (for example, a page with the message “oops something went wrong”) whenever we encountered a server_error
, authorization_error
, or authorization_not_found
. However, we also wanted the flexibility to be able to handle an error in a specific component if we wanted to.
For example, if a user was typing something into a search bar and something went wrong, we wanted to display an error message in-context, rather than flash over to an error page.
To achieve this, we first created a component called GraphqlErrorHandler
that would sit between apollo-client
’s Query
and Mutation
components and their children to be rendered out. This component checked for error codes in the response threw an exception if it identified a code we cared about:
To use the GraphqlErrorHandler
, we wrapped apollo-client’s Query
and Mutation
components:
Our feature component then used our own Query
component instead of directly accessing react-apollo
:
Now that our React app was throwing exceptions when the server returned errors, we wanted to handle these exceptions and map them to the appropriate behavior.
Remember from earlier that our goal was to default to displaying global error pages (for example, a page with the message “oops something went wrong”), but still have the flexibility to handle an error locally within any component if we desired.
React error boundaries provide a fantastic way of doing this. Error boundaries are React components that can catch JavaScript errors anywhere in their child component tree so you can handle them with custom behavior.
We created an error boundary called GraphqlErrorBoundary
that would catch any server-related exceptions and display the appropriate error page:
We use the error boundary as a wrapper for all of our app’s components:
Error boundaries can be used deeper in the component tree if we want to handle errors in a component instead of rendering an error page.
For example, here’s what it’d look if we wanted custom error handling behavior in our component from earlier:
Wrap-up
GraphQL is still relatively new, and error handling is a common challenge that developers seem to be running into. By using standardized error codes in our GraphQL responses, we can communicate errors to clients in a useful and intuitive way. In our React apps, error boundaries provide a great way to standardize our app’s error handling behavior while still having flexibility when we need it.
Learn to code for free. freeCodeCamp’s open source curriculum has helped more than 40,000 people get jobs as developers. Get started
react-error-boundary
Simple reusable React error boundary component
The problem
React v16 introduced the
concept of “error boundaries”.
This solution
This component provides a simple and reusable wrapper that you can use to wrap
around your components. Any rendering errors in your components hierarchy can
then be gracefully handled.
Reading this blog post will help you understand what react-error-boundary does
for you:
Use react-error-boundary to handle errors in React
– How to simplify your React apps by handling React errors effectively with
react-error-boundary
Table of Contents
- Installation
- Usage
- Error Recovery
- API
ErrorBoundary
propsuseErrorHandler(error?: unknown)
- Issues
- 🐛 Bugs
- 💡 Feature Requests
- LICENSE
Installation
This module is distributed via npm which is bundled with node and
should be installed as one of your project’s dependencies
:
npm install --save react-error-boundary
Usage
The simplest way to use <ErrorBoundary>
is to wrap it around any component
that may throw an error. This will handle errors thrown by that component and
its descendants too.
import {ErrorBoundary} from 'react-error-boundary' function ErrorFallback({error, resetErrorBoundary}) { return ( <div role="alert"> <p>Something went wrong:</p> <pre>{error.message}</pre> <button onClick={resetErrorBoundary}>Try again</button> </div> ) } const ui = ( <ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => { // reset the state of your app so the error doesn't happen again }} > <ComponentThatMayError /> </ErrorBoundary> )
You can react to errors (e.g. for logging) by providing an onError
callback:
import {ErrorBoundary} from 'react-error-boundary' const myErrorHandler = (error: Error, info: {componentStack: string}) => { // Do something with the error // E.g. log to an error logging client here } const ui = ( <ErrorBoundary FallbackComponent={ErrorFallback} onError={myErrorHandler}> <ComponentThatMayError /> </ErrorBoundary>, )
You can also use it as a
higher-order component:
import {withErrorBoundary} from 'react-error-boundary' const ComponentWithErrorBoundary = withErrorBoundary(ComponentThatMayError, { FallbackComponent: ErrorBoundaryFallbackComponent, onError(error, info) { // Do something with the error // E.g. log to an error logging client here }, }) const ui = <ComponentWithErrorBoundary />
Error Recovery
In the event of an error if you want to recover from that error and allow the
user to «try again» or continue with their work, you’ll need a way to reset the
ErrorBoundary’s internal state. You can do this various ways, but here’s the
most idiomatic approach:
function ErrorFallback({error, resetErrorBoundary}) { return ( <div role="alert"> <p>Something went wrong:</p> <pre>{error.message}</pre> <button onClick={resetErrorBoundary}>Try again</button> </div> ) } function Bomb() { throw new Error('💥 CABOOM 💥') } function App() { const [explode, setExplode] = React.useState(false) return ( <div> <button onClick={() => setExplode(e => !e)}>toggle explode</button> <ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => setExplode(false)} resetKeys={[explode]} > {explode ? <Bomb /> : null} </ErrorBoundary> </div> ) }
So, with this setup, you’ve got a button which when clicked will trigger an
error. Clicking the button again will trigger a re-render which recovers from
the error (we no longer render the <Bomb />
). We also pass the resetKeys
prop which is an array of elements for the ErrorBoundary
to check each render
(if there’s currently an error state). If any of those elements change between
renders, then the ErrorBoundary
will reset the state which will re-render the
children.
We have the onReset
prop so that if the user clicks the «Try again» button we
have an opportunity to re-initialize our state into a good place before
attempting to re-render the children.
This combination allows us both the opportunity to give the user something
specific to do to recover from the error, and recover from the error by
interacting with other areas of the app that might fix things for us. It’s hard
to describe here, but hopefully it makes sense when you apply it to your
specific scenario.
API
ErrorBoundary
props
children
This is what you want rendered when everything’s working fine. If there’s an
error that React can handle within the children of the ErrorBoundary
, the
ErrorBoundary
will catch that and allow you to handle it gracefully.
FallbackComponent
This is a component you want rendered in the event of an error. As props it will
be passed the error
and resetErrorBoundary
(which will reset the error
boundary’s state when called, useful for a «try again» button when used in
combination with the onReset
prop).
This is required if no fallback
or fallbackRender
prop is provided.
fallbackRender
This is a render-prop based API that allows you to inline your error fallback UI
into the component that’s using the ErrorBoundary
. This is useful if you need
access to something that’s in the scope of the component you’re using.
It will be called with an object that has error
and resetErrorBoundary
:
const ui = ( <ErrorBoundary fallbackRender={({error, resetErrorBoundary}) => ( <div role="alert"> <div>Oh no</div> <pre>{error.message}</pre> <button onClick={() => { // this next line is why the fallbackRender is useful resetComponentState() // though you could accomplish this with a combination // of the FallbackCallback and onReset props as well. resetErrorBoundary() }} > Try again </button> </div> )} > <ComponentThatMayError /> </ErrorBoundary> )
I know what you’re thinking: I thought we ditched render props when hooks came
around. Unfortunately, the current React Error Boundary API only supports class
components at the moment, so render props are the best solution we have to this
problem.
This is required if no FallbackComponent
or fallback
prop is provided.
fallback
In the spirit of consistency with the React.Suspense
component, we also
support a simple fallback
prop which you can use for a generic fallback. This
will not be passed any props so you can’t show the user anything actually useful
though, so it’s not really recommended.
const ui = ( <ErrorBoundary fallback={<div>Oh no</div>}> <ComponentThatMayError /> </ErrorBoundary> )
onError
This will be called when there’s been an error that the ErrorBoundary
has
handled. It will be called with two arguments: error
, info
.
onReset
This will be called immediately before the ErrorBoundary
resets it’s internal
state (which will result in rendering the children
again). You should use this
to ensure that re-rendering the children will not result in a repeat of the same
error happening again.
onReset
will be called with whatever resetErrorBoundary
is called with.
Important: onReset
will not be called when reset happens from a change
in resetKeys
. Use onResetKeysChange
for that.
resetKeys
Sometimes an error happens as a result of local state to the component that’s
rendering the error. If this is the case, then you can pass resetKeys
which is
an array of values. If the ErrorBoundary
is in an error state, then it will
check these values each render and if they change from one render to the next,
then it will reset automatically (triggering a re-render of the children
).
See the recovery examples above.
onResetKeysChange
This is called when the resetKeys
are changed (triggering a reset of the
ErrorBoundary
). It’s called with the prevResetKeys
and the resetKeys
.
useErrorHandler(error?: unknown)
React’s error boundaries feature is limited in that the boundaries can only
handle errors thrown during React’s lifecycles. To quote
the React docs on Error Boundaries:
Error boundaries do not catch errors for:
- Event handlers
(learn more)- Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
- Server side rendering
- Errors thrown in the error boundary itself (rather than its children)
This means you have to handle those errors yourself, but you probably would like
to reuse the error boundaries you worked hard on creating for those kinds of
errors as well. This is what useErrorHandler
is for.
There are two ways to use useErrorHandler
:
const handleError = useErrorHandler()
: callhandleError(theError)
useErrorHandler(error)
: useful if you are managing the error state yourself
or get it from another hook.
Here’s an example:
import { useErrorHandler } from 'react-error-boundary' function Greeting() { const [greeting, setGreeting] = React.useState(null) const handleError = useErrorHandler() function handleSubmit(event) { event.preventDefault() const name = event.target.elements.name.value fetchGreeting(name).then( newGreeting => setGreeting(newGreeting), handleError, ) } return greeting ? ( <div>{greeting}</div> ) : ( <form onSubmit={handleSubmit}> <label>Name</label> <input id="name" /> <button type="submit">get a greeting</button> </form> ) }
Note, in case it’s not clear what’s happening here, you could also write
handleSubmit
like this:
function handleSubmit(event) { event.preventDefault() const name = event.target.elements.name.value fetchGreeting(name).then( newGreeting => setGreeting(newGreeting), error => handleError(error), ) }
Alternatively, let’s say you’re using a hook that gives you the error:
import { useErrorHandler } from 'react-error-boundary' function Greeting() { const [name, setName] = React.useState('') const {greeting, error} = useGreeting(name) useErrorHandler(error) function handleSubmit(event) { event.preventDefault() const name = event.target.elements.name.value setName(name) } return greeting ? ( <div>{greeting}</div> ) : ( <form onSubmit={handleSubmit}> <label>Name</label> <input id="name" /> <button type="submit">get a greeting</button> </form> ) }
In this case, if the error
is ever set to a truthy value, then it will be
propagated to the nearest error boundary.
In either case, you could handle those errors like this:
const ui = ( <ErrorBoundary FallbackComponent={ErrorFallback}> <Greeting /> </ErrorBoundary> )
And now that’ll handle your runtime errors as well as the async errors in the
fetchGreeting
or useGreeting
code.
Issues
Looking to contribute? Look for the Good First Issue
label.
🐛 Bugs
Please file an issue for bugs, missing documentation, or unexpected behavior.
See Bugs
💡 Feature Requests
Please file an issue to suggest new features. Vote on feature requests by adding
a 👍. This helps maintainers prioritize what to work on.
See Feature Requests
LICENSE
MIT
Let’s face it. Nobody wants to see a broken, empty page while surfing the web. It leaves you stranded and confused. You don’t know what happened or what caused it, leaving you with a bad impression of the website.
It is often better to communicate the error and let the user continue to use the app. The user will get less of a bad impression and can continue to use its features.
In today’s post, we’ll go through different ways to handle errors in React applications.
The Classic ‘Try and Catch’ Method in React
If you’ve used JavaScript, you’ve probably had to write a ‘try and catch’ statement. To make sure we’re on board with what it is, here’s one:
try {
somethingBadMightHappen();
} catch (error) {
console.error("Something bad happened");
console.error(error);
}
It is a great tool to catch misbehaving code and ensure our app doesn’t blow up into smithereens. To be more realistic and close to the React world as possible, let’s see an example of how you’d use this in your app:
const fetchData = async () => {
try {
return await fetch("https://some-url-that-might-fail.com");
} catch (error) {
console.error(error); // You might send an exception to your error tracker like AppSignal
return error;
}
};
When doing network calls in React, you’d usually use the try...catch
statement. But why? Unfortunately, try...catch
only works on imperative code. It does not work on declarative code like the JSX we are writing in our components. So that is why you don’t see a massive try...catch
wrapping our whole app. It just won’t work.
So, what do we do? Glad you asked. In React 16, a new concept got introduced — React Error Boundaries. Let’s dig into what they are.
React Error Boundaries
Before we get into error boundaries, let us first see why they are necessary. Imagine you had a component like this:
const CrashableComponent = (props) => {
return <span>{props.iDontExist.prop}</span>;
};
export default CrashableComponent;
If you try to render this component somewhere, you’ll get an error like this one:
Not only that, the whole page will be blank, and the user won’t be able to do or see anything. But what happened? We tried to access a property iDontExist.prop
, which doesn’t exist (we don’t pass it to the component). This is a banal example, but it shows that we cannot catch these errors with the try...catch
statement.
This whole experiment brings us to error boundaries. Error boundaries are React components that catch JavaScript errors anywhere in their child component tree. Then, they log those caught 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.
An error boundary is a class component that defines either (or both) of the lifecycle methods static getDerivedStateFromError()
or componentDidCatch()
.
static getDerivedStateFromError()
renders a fallback UI after an error has been thrown.
componentDidCatch()
can log error information to your service provider (like AppSignal) or to a browser console.
Here’s an example of how information about a React error looks in AppSignal’s ‘issue list’:
Let’s see a typical error boundary component:
import { Component } from "react";
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return {
hasError: true,
error,
};
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service like AppSignal
// logErrorToMyService(error, errorInfo);
}
render() {
const { hasError, error } = this.state;
if (hasError) {
// You can render any custom fallback UI
return (
<div>
<p>Something went wrong 😭</p>
{error.message && <span>Here's the error: {error.message}</span>}
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
We can use ErrorBoundary
like so:
<ErrorBoundary>
<CrashableComponent />
</ErrorBoundary>
Now, when we open our app, we will get a working app with the following:
That is precisely what we want. We want our app to remain functional when an error occurs. But we also want to inform the user (and our error tracking service) about the error.
Beware that using an error boundary is not a silver bullet. Error boundaries do not catch errors for:
- Event handlers
- Asynchronous code (e.g.
setTimeout
orrequestAnimationFrame
callbacks) - Server-side rendering
- Errors that are thrown in the error boundary itself (rather than its children)
You still need to use the try...catch
statement for these fellas. So, let’s go ahead and show how you can do that.
Error Catching in Event Handlers
As mentioned before, error boundaries can’t help us when an error is thrown inside an event handler. Let’s see how we can handle those. Below is a small button component that throws an error when you click it:
import { useState } from "react";
const CrashableButton = () => {
const [error, setError] = useState(null);
const handleClick = () => {
try {
throw Error("Oh no :(");
} catch (error) {
setError(error);
}
};
if (error) {
return <span>Caught an error.</span>;
}
return <button onClick={handleClick}>Click Me To Throw Error</button>;
};
export default CrashableButton;
Notice that we have a try and catch block inside handleClick
that ensures our error is caught. If you render the component and try to click it, this happens:
We have to do the same in other cases, like in setTimeout
calls.
Error Catching in setTimeout
Calls
Imagine we have a similar button component, but this one calls setTimeout
when clicked. Here’s how it looks:
import { useState } from "react";
const SetTimeoutButton = () => {
const [error, setError] = useState(null);
const handleClick = () => {
setTimeout(() => {
try {
throw Error("Oh no, an error :(");
} catch (error) {
setError(error);
}
}, 1000);
};
if (error) {
return <span>Caught a delayed error.</span>;
}
return (
<button onClick={handleClick}>Click Me To Throw a Delayed Error</button>
);
};
export default SetTimeoutButton;
After 1,000 milliseconds, the setTimeout
callback will throw an error. Luckily, we wrap that callback logic in try...catch
, and setError
in the component. That way, no stack trace is shown in the browser console. Also, we communicate the error to the user. Here’s how it looks in the app:
That is all well and good, as we got our app’s pages up and running despite errors popping all over the place in the background. But is there an easier way to handle errors without writing custom error boundaries? You bet there is, and of course, it comes in the form of a JavaScript package. Let me introduce you to the react-error-boundary
.
JavaScript’s react-error-boundary
Package
You can pop that library inside your package.json
faster than ever with:
npm install --save react-error-boundary
Now, you’re ready to use it. Remember the ErrorBoundary
component we made? You can forget about it because this package exports its own. Here’s how to use it:
import { ErrorBoundary } from "react-error-boundary";
import CrashableComponent from "./CrashableComponent";
const FancyDependencyErrorHandling = () => {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error) => {
// You can also log the error to an error reporting service like AppSignal
// logErrorToMyService(error, errorInfo);
console.error(error);
}}
>
<CrashableComponent />
</ErrorBoundary>
);
};
const ErrorFallback = ({ error }) => (
<div>
<p>Something went wrong 😭</p>
{error.message && <span>Here's the error: {error.message}</span>}
</div>
);
export default FancyDependencyErrorHandling;
In the example above, we render the same CrashableComponent
, but this time, we use the ErrorBoundary
component from the react-error-boundary
library. It does the same thing as our custom one, except that it receives the FallbackComponent
prop plus the onError
function handler. The result is the same as we had with our custom ErrorBoundary
component, except you don’t have to worry about maintaining it since you’re using an external package.
One great thing about this package is that you can easily wrap your function components into a withErrorBoundary
making it a higher-order component (HOC). Here’s how that looks:
import { withErrorBoundary } from "react-error-boundary";
const CrashableComponent = (props) => {
return <span>{props.iDontExist.prop}</span>;
};
export default withErrorBoundary(CrashableComponent, {
FallbackComponent: () => <span>Oh no :(</span>,
});
Nice, you’re good to go now to capture all those errors bugging you.
But maybe you don’t want another dependency in your project. Can you achieve it yourself? Of course you can. Let’s see how it can be done.
Using Your Own React Boundaries
You can achieve a similar, if not the same, effect you get from react-error-boundary
. We already showed a custom ErrorBoundary
component, but let’s improve it.
import { Component } from "react";
export default class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return {
hasError: true,
error,
};
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service like AppSignal
// logErrorToMyService(error, errorInfo);
}
render() {
const { hasError, error } = this.state;
if (hasError) {
// You can render any custom fallback UI
return <ErrorFallback error={error} />;
}
return this.props.children;
}
}
const ErrorFallback = ({ error }) => (
<div>
<p>Something went wrong 😭</p>
{error.message && <span>Here's the error: {error.message}</span>}
</div>
);
const errorBoundary = (WrappedComponent) => {
return class extends ErrorBoundary {
render() {
const { hasError, error } = this.state;
if (hasError) {
// You can render any custom fallback UI
return <ErrorFallback error={error} />;
}
return <WrappedComponent {...this.props} />;
}
};
};
export { errorBoundary };
Now you get the ErrorBoundary
and the HOC errorBoundary
that you can use across your app. Extend and play around with it as much as you want. You can make them receive custom fallback components to customize how you recover from each error. You can also make them receive an onError
prop and later call it inside componentDidCatch
. The possibilities are endless.
But one thing is for sure — you didn’t need that dependency after all. I bet writing your own error boundary will bring a sense of achievement, and you’ll get to understand it better. Also, who knows what ideas you might get when you’re trying to customize it.
Summing Up: Get Started with React Error Handling
Thanks for reading this blog post about handling errors in React. I hope you had as much fun reading and trying things out as I did writing it. You can find all the code, with examples, in the GitHub repo I created.
A quick rundown of the things we went through:
- React Error boundaries are great for catching errors in declarative code (e.g., inside their child component tree).
- For other cases, you need to use a
try...catch
statement (e.g., async calls likesetTimeout
, event handlers, server-side rendering, and errors thrown in the error boundary itself). - A library like
react-error-boundary
can help you write less code. - You can also run your own error boundary and customize it as much as you want.
That is all, folks. Thanks for tuning in, and catch you in the next one!
P.S. If you liked this post, subscribe to our JavaScript Sorcery list for a monthly deep dive into more magical JavaScript tips and tricks.
P.P.S. If you need an APM for your Node.js app, go and check out the AppSignal APM for Node.js.