In using the errorHandler element, I am noticing that errors thrown from within Promises are not being handled.
A sandbox reproducing the issue can be found: here.
In short, if you throw an error directly, the errorHandler is invoked:
function SyncError() {
throw new Response('sync error', { status: 500 });
return <p>You shouldn't see this</p>;
}
However, the error is thrown in a Promise, even if caught and rethrown, it is treated as unhandled:
function AsyncError() {
Promise.reject(new Response('sync error', { status: 500 }))
.catch((e) => {
throw e;
});
return <p>You shouldn't see this</p>;
}
These are admittedly very contrived examples, but the actual behavior I'm trying to control is how to have the errors raised from within useEffect be caught by the errorElement. In other words, something like:
useEffect(() => {
someMethodThatThrows()
.then(...)
.catch((e) => {
// What do I do?
});
}, []);
I have a workaround in the sandbox above that involves setting local state to the error, but that seems pretty silly.
Is this a bug or the expected behavior? If expected behavior, are there any cleaner workarounds?
UPDATE 10/10/2022
After further review, it looks like this is expected behavior. Found this on the react router site:
The vast majority of your application errors are handled automatically by React Router. It will catch any errors that are thrown while:
- rendering
- loading data
- updating data
In practice, this is pretty much every error in your app except those thrown in event handlers (
<button onClick>) oruseEffect. React Router apps tend to have very few of either.
From what I understand the errorElement works with the new Data API and works in conjunction with the loader handler.
In the case of your "async error" the error needs to be thrown when loading the route, not as an unexpected side-effect in the component after it's already been matched and rendered.
Example:
const router = createBrowserRouter(
createRoutesFromElements(
<Route path="/" element={<Layout />} errorElement={<ErrorBoundary />}>
<Route path="no-error" element={<NoError />} />
<Route path="sync-error" element={<SyncError />} />
<Route
path="async-error"
element={<AsyncError />}
loader={async (props) => {
// handle loading requests and throwing errors here
console.log({ props });
throw new Response('sync error', { status: 500 });
}}
/>
<Route path="async-error-hack" element={<AsyncErrorHack />} />
</Route>
)
);
...
function AsyncError() {
// Not here
// Promise.reject(new Response('sync error', { status: 500 })).catch((e) => {
// throw e;
// });
return <p>You shouldn't see this</p>;
}
If errors are being thrown or are occurring after the component has mounted then these should be handled gracefully in the component (i.e. try/catch, etc...) or by an actual Error Boundary component.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With