Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does one invoke the errorHandler from async operations?

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>) or useEffect. React Router apps tend to have very few of either.

like image 681
Dave Mateer Avatar asked Nov 03 '25 22:11

Dave Mateer


1 Answers

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.

like image 154
Drew Reese Avatar answered Nov 06 '25 16:11

Drew Reese



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!