How to handle errors gracefully in Remix

How to use ErrorBoundary and CatchBoundary in error handling

Thursday, December 16, 2021

stock market crash

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.

.tsx
12345import type { ErrorBoundaryComponent } from "remix"; export const ErrorBoundary: ErrorBoundaryComponent = ({ error }) => { return <div>ERROR: {error.message}</div>;};

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


.tsx
123export function loader() { throw new Error('I am a failure!')}
.html
<div>ERROR: "I am a failure!"</div>
.tsx
123export function action() { return fetch("http://a.b.c");}
.html
<div>ERROR: "request to http://a.b.c/ failed, reason: getaddrinfo ENOTFOUND a.b.c"</div>
.tsx
1234export function loader() { // syntax error someCodeInBackend;}
.html
<div>ERROR: "someCodeInBackend is not defined"</div>
.tsx
12345export default function Index() { someCodeInReact; return <div>{/*...*/}</div>;}
.html
<div>ERROR: "someCodeInReact is not defined"</div>

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.

.tsx
123456789101112export function CatchBoundary() { const caught = useCatch(); return ( <> <div> ERROR: {caught.statusText} {caught.status} </div> <div>{caught.data.message}</div> </> );}
.tsx
123export function loader() { throw json({ message: "Some custom message" }, 501);}

will yield to

.html
<div>ERROR: Not Implemented 501</div><div>Some custom message</div>

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

.tsx
123export function action() { throw json("Some custom message", 501);}

will yield to

.html
<div>ERROR: Not Implemented 501</div><div></div>

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.