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>;
};
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!')
}
<div>ERROR: "I am a failure!"</div>
- Unhandled errors
export function action() {
return fetch("http://a.b.c");
}
<div>ERROR: "request to http://a.b.c/ failed, reason: getaddrinfo ENOTFOUND a.b.c"</div>
- Other errors in backend
export function loader() {
// syntax error
someCodeInBackend;
}
<div>ERROR: "someCodeInBackend is not defined"</div>
- Errors in React land
export default function Index() {
someCodeInReact;
return <div>{/*...*/}</div>;
}
<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
.
export function CatchBoundary() {
const caught = useCatch();
return (
<>
<div>
ERROR: {caught.statusText} {caught.status}
</div>
<div>{caught.data.message}</div>
</>
);
}
export function loader() {
throw json({ message: "Some custom message" }, 501);
}
will yield to
<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.
export function action() {
throw json("Some custom message", 501);
}
will yield to
<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
Handling route 404 error
Handling error in an action
Try to fill in the form, then click submit to see the 401
error.
Handing error in a nested route
Propagating error from /test
to the root route
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 rootErrorBoundary
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)