How to Handle Errors Efficiently in React
Tags: code, react, javascript, tutorial
Published: 2021-10-26 14:20:00

---
title: How to Handle Errors Efficiently in React
published: true
date: 2021-10-26 14:20:00 UTC
tags: code,react,javascript,tutorial
canonical_url: https://x-team.com/blog/errors-in-react/
---
Errors are an inevitable part of programming. Error boundaries are React's way of handling JavaScript errors in React components. Introduced in React 16, error boundaries are crucial for minimizing the moments when everything seems to break and no one understands why.

You might think we could just use a `try...catch` statement and print the error trace, but `try...catch` only works for imperative code and not the declarative code we write in React components. Additionally, even with a `try...catch` statement, it's hard to know where the error came from, which line of code made the app break, or which files were involved.

This article will explain how you can use error boundaries to get full control of your errors in JavaScript and React.

## What Happened and Where Did It Happen?


An effective error boundary tells us both what happened and where it happened. For that, we need the `Error` object. If you have good error handling with an `Error` object, you'll get a full error stack. Let's showcase this with a button that crashes our app and prints an error:
```react
import { useState } from "react";
 
const App = () => {
  const [error, setError] = useState(Error());
 
  const throwError = () => {
    throw Error("I'm an error");
  };
 
  const crash = () => {
    try {
      throwError();
    } catch (e) {
      setError(e);
      console.error(e);
    }
  };
 
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        marginTop: 80,
      }}
    >
      <button onClick={crash}>Hi! I'm a button that crashes</button>
      <p style={{ color: "red" }}>{error.message}</p>
    </div>
  );
};
 
export default App;
```
![How to Handle Errors Efficiently in React](https://res.cloudinary.com/dukp6c7f7/image/upload/f_auto,fl_lossy,q_auto/s3-ghost//2021/10/Image-1.png)
You can see that everything is working just fine. You understand what happened and where it happened. The error message shows up in the UI and nothing has crashed. But what if we took the same approach for an HTTP request with axios?
```react
  const crash = async () => {
    try {
      await axios.get("https://urlthatdoesnotexists.url")
    } catch (e) {
      setError(e);
      console.error(e);
    }
  };
```
![How to Handle Errors Efficiently in React](https://res.cloudinary.com/dukp6c7f7/image/upload/f_auto,fl_lossy,q_auto/s3-ghost//2021/10/Image-2.png)
This is worse. Now, we know what is happening, but not where it's happening. Thankfully, you can get around this by logging a static instance of an `Error` object instead of logging the error itself.
```react
 const crash = async () => {
    try {
      await axios.get("https://urlthatdoesnotexists.url");
    } catch (e) {
      setError(e);
      console.error(Error(e.message ?? e));
    }
  };
```
![How to Handle Errors Efficiently in React](https://res.cloudinary.com/dukp6c7f7/image/upload/f_auto,fl_lossy,q_auto/s3-ghost//2021/10/Image-3.png)

## Do We Need More?


`try...catch` statements can become really messy quickly, particularly if you're dealing with nested exceptions. You want to make sure that your application doesn't break, regardless of what your code is doing.
You can make everything much simpler with two handler functions that receive callbacks as their arguments: one function for synchronous calls and the other for asynchronous calls.
```react
//handlers/exceptions.js
export const execute = (callback) => {
  try {
    const res = callback();
    return [res, null];
  } catch (err) {
    console.error(Error(err.message ?? err));
    // You can also log error messages to an error reporting service here
    return [null, err];
  }
};
 
export const executeAsync = async (callback) => {
  try {
    const res = await callback();
    return [res, null];
  } catch (err) {
    console.error(Error(err.message ?? err));
    // You can also log error messages to an error reporting service here
    return [null, err];
  }
};
```
Now, let's call the corresponding function in our app:
```react
  const [error, setError] = useState(new Error());
 
  const fetchData = async () =>
    await axios.get("http://urlthatdoesnotexist.url");
 
  const crash = async () => {
    const [res, err] = await executeAsync(fetchData);
    if (err) return setError(err);
 
    //do something with result
  };
```
![How to Handle Errors Efficiently in React](https://res.cloudinary.com/dukp6c7f7/image/upload/f_auto,fl_lossy,q_auto/s3-ghost//2021/10/Image-4.png)
This approach allows us to have an error-prone application without having to wrap everything in endless `try...catch` statements. It's easy to make changes to the error handling process, too. All we need to do is edit the handler, which will update every component where error handling is needed.
## Rendering Errors


What would happen if the error happens in the JSX part of our component and crashes our entire application? Let's say we're accessing a property with a null value (yes, we can control that using `Optional Chaining`, but let's pretend we can't).
```react
const App = () => {
  const [error, setError] = useState({ message: "I'm an error message" });
 
  const crash = () => {
    setError(null);
  };
 
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        marginTop: 80,
      }}
    >
      <button onClick={crash}>Hi! I'm a button that crashes</button>
      <p style={{ color: "red" }}>{error.message}</p>
    </div>
  );
};
 
export default App;
```
When we click the button that crashes, we're served a blank page.
![How to Handle Errors Efficiently in React](https://res.cloudinary.com/dukp6c7f7/image/upload/f_auto,fl_lossy,q_auto/s3-ghost//2021/10/Image-5.png)
We can control this by introducing an HOC that wraps our component within an error boundary.
```react
import { useState } from "react";
import { errorBoundary } from "./ErrorBoundary";
 
const App = () => {
  const [error, setError] = useState({ message: "I'm an error message" });
 
  const crash = () => {
    setError(null);
  };
 
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        marginTop: 80,
      }}
    >
      <button onClick={crash}>Hi! I'm a button that crashes</button>
      <p style={{ color: "red" }}>{error.message}</p>
    </div>
  );
};
 
export default errorBoundary(App);
```
But, if we don't want to use an HOC, we can also wrap a component with an `ErrorBoundary` component. The result will be the same:
```react
import React from "react";
import ReactDOM from "react-dom";
import App from "./AppCrash";
import ErrorBoundary from "./ErrorBoundary";
 
ReactDOM.render(
  <React.StrictMode>
    <ErrorBoundary>
      <App />
    </ErrorBoundary>
  </React.StrictMode>,
  document.getElementById("root")
);
```
![How to Handle Errors Efficiently in React](https://res.cloudinary.com/dukp6c7f7/image/upload/f_auto,fl_lossy,q_auto/s3-ghost//2021/10/Image-6.png)
Now, we're controlling the exception. The app doesn't break and we're showing what we want to show when something crashes.
_Error boundary file (here you are exporting both the Component and the HOC):_
```react
// hoc/ErrorBoundary.js
import { Component } from "react";
 
const ErrorView = ({ error, errorInfo }) => (
  <div>
    <h2>Something went wrong.</h2>
    <details>
      {error && error.toString()}
      <br />
      {errorInfo.componentStack}
    </details>
  </div>
);
 
export default class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { error: null, errorInfo: null };
  }
 
  componentDidCatch(error, errorInfo) {
    // Catch errors in any components below and re-render with error message
    this.setState({
      error: error,
      errorInfo: errorInfo,
    });
    // You can also log error messages to an error reporting service here
  }
 
  render() {
    const { error, errorInfo } = this.state;
    if (errorInfo) {
      // Error path
      return <ErrorView {...{ error, errorInfo }} />;
    }
    // Normally, just render children
    return this.props.children;
  }
}
 
export function errorBoundary(WrappedComponent) {
  return class extends ErrorBoundary {
    render() {
      const { error, errorInfo } = this.state;
      if (errorInfo) {
        // Error path
        return <ErrorView {...{ error, errorInfo }} />;
      }
      //Normally, just render wrapped component
      return <WrappedComponent {...this.props} />;
    }
  };
}
```
## To Wrap Up


Every high-quality application must have great error handling to deal with unexpected events. Errors should be appropriately logged so they have no impact on the user's experience and so your colleagues (and yourself) can determine the root cause of any crash. Error boundaries are how you do this in React.