DEV Community

Carlo Gino Catapang
Carlo Gino Catapang

Posted on • Edited on • Originally published at l.carlogino.com

How to handle errors gracefully in Remix

Using ErrorBoundary and CatchBoundary in error handling

How Remix handles errors

From the Remix official documentation

Remix will automatically catch errors and render the nearest error boundary for errors thrown while:

  • rendering in the browser
  • rendering on the server
  • in a loader during the initial server rendered document request
  • in an action during the initial server rendered document request
  • in a loader during a client-side transition in the browser (Remix serializes the error and sends it over the network to the browser)
  • in an action during a client-side transition in the browser

What can we use to handle errors in Remix?

Since Remix is still a JavasScript framework, we can still use JavaScript constructs to catch errors such as try/cath.
This blog will only discuss how to catch errors using ErrorBoundary and CatchBoundary.

Using ErrorBoundary

From the official Docs

An ErrorBoundary is a React component that renders whenever there is an error anywhere on the route, either during rendering or during data loading.

Here is an example of using the ErrorBoundary component to catch errors in Remix.

import type { ErrorBoundaryComponent } from "remix";

export const ErrorBoundary: ErrorBoundaryComponent = ({ error }) => {
  return <div>ERROR: {error.message}</div>;
};
Enter fullscreen mode Exit fullscreen mode

Using the ErrorBoundary above, Here are some examples of errors we can catch and the corresponding result.

- Explicitly thrown errors
export function loader() {
  throw new Error('I am a failure!')
}
Enter fullscreen mode Exit fullscreen mode
<div>ERROR: "I am a failure!"</div>
Enter fullscreen mode Exit fullscreen mode
- Unhandled errors
export function action() {
    return fetch("http://a.b.c");
}
Enter fullscreen mode Exit fullscreen mode
<div>ERROR: "request to http://a.b.c/ failed, reason: getaddrinfo ENOTFOUND a.b.c"</div>
Enter fullscreen mode Exit fullscreen mode
- Other errors in backend
export function loader() {
  // syntax error
  someCodeInBackend;
}
Enter fullscreen mode Exit fullscreen mode
<div>ERROR: "someCodeInBackend is not defined"</div>
Enter fullscreen mode Exit fullscreen mode
- Errors in React land
export default function Index() {
  someCodeInReact;

  return <div>{/*...*/}</div>;
}
Enter fullscreen mode Exit fullscreen mode
<div>ERROR: "someCodeInReact is not defined"</div>
Enter fullscreen mode Exit fullscreen mode

Using CatchBoundary

A CatchBoundary is a React component that renders whenever an action or loader throws a Response.

It works in combination with useCatch. The hook will allow us to get a reference to the response's data, status, and statusText.

export function CatchBoundary() {
  const caught = useCatch();

    return (
    <>
      <div>
        ERROR: {caught.statusText} {caught.status}
      </div>
      <div>{caught.data.message}</div>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode
export function loader() {
  throw json({ message: "Some custom message" }, 501);
}
Enter fullscreen mode Exit fullscreen mode

will yield to

<div>ERROR: Not Implemented 501</div>
<div>Some custom message</div>
Enter fullscreen mode Exit fullscreen mode

We can also use other data formats such as plain string, but we need to make sure our CatchBoundary can handle them properly.

export function action() {
  throw json("Some custom message", 501);
}
Enter fullscreen mode Exit fullscreen mode

will yield to

<div>ERROR: Not Implemented 501</div>
<div></div>
Enter fullscreen mode Exit fullscreen mode

In which cases should we use one over the other?

We should use CatchBoundary if we care about the context of the error, such as status codes or the custom data related to the error; in another case, we use ErrorBoundary.

The good thing is we can even use them together. So, for example, if there's an error in the CatchBoundary, that error will be caught by the ErrorBoundary.

Live examples

Handling global 404 error

/non-exisiting-page

Handling route 404 error

/words/404

Handling error in an action

Try to fill in the form, then click submit to see the 401 error.

/words/add

Handing error in a nested route

/words/edit/404

Propagating error from /test to the root route

/test

Additional note:

Your root ErrorBoundary should not throw an error in a production environment since there's nothing else to catch the error. If the root ErrorBoundary can throw an error, the user might see something like this

Conclusion

I find using ErrorBoundary and CatchBoundary an easy way to handle route errors in Remix as it allows us to have fine-grained control on how we want to display errors in our pages. In my opinion, it improves the readability of what we are trying to achieve as there is the precise placement of the success and error cases.

Top comments (0)