DEV Community

loading...
Cover image for react-error-boundary an example

react-error-boundary an example

justincalleja profile image Justin Calleja ・2 min read

(Photo by Markus Spiske on Unsplash)

Today I used react-error-boundary and thought I'd blog about it - just it's usage in a project.

Basically, I had something like the following in a component:

        {selectIsRetryInitialDataFetchRequired(state) ? (
          <X {...propsA} />
        ) : selectIsLoginAgainRequired(state) ? (
          <X {...propsB} />
        ) : selectIsContactCustomerSupportRequired(state) ? (
          <X {...propsC} />
        ) : 
// etc... conditions to know when data loaded successfully
// render main UI
// fallback render spinner
Enter fullscreen mode Exit fullscreen mode

The select functions are deriving values from state to know - based off of the responses of an HTTP client - what error UI to render (e.g. a 500 or JS fetch error in any of the requests made to get the data to be able to sensibly show the main UI would be an isRetryInitialDataFetchRequired of true).

Today I got the requirement that one of the HTTP requests being made will have another error response I'm to handle - still an error UI, but resolving it differs so it requires a different UI.

To be fair, this component (to me), is still quite easy to understand and reason about, especially since X, the error handling UI component is just the same component with variations on text content and button actions. The meat is in the "happy path" which brings together the main components used by the micro app (a small React app that is loaded by another app in a very specific use case).

The addition to the error UIs, though, has a little more going on and it's starting to get annoying to keep everything there.

Long story short, I ended up replacing those error branches with:

  useErrorHandler(selectIsErrorFallbackRenderRequired(state));
Enter fullscreen mode Exit fullscreen mode

where selectIsErrorFallbackRenderRequired is just a combination of the previous selectors (and soon to have another):

export const selectIsErrorFallbackRenderRequired = (state) =>
  selectIsRetryInitialDataFetchRequired(state) ||
  selectIsLoginAgainRequired(state) ||
  selectIsContactCustomerSupportRequired(state);
Enter fullscreen mode Exit fullscreen mode

useErrorHandler is from react-error-boundary. When the selector returns true, it ends up rendering its closest ErrorBoundary… so obviously, I also had to add one of those at the root level:

      <ErrorBoundary
        fallback={<ErrorFallback state={state} {...otherProps} />}
        resetKeys={[selectIsErrorFallbackRenderRequired(state)]}
      >
        <App state={state} />
      </ErrorBoundary>
Enter fullscreen mode Exit fullscreen mode

ErrorFallback is now responsible for rendering any error UI the micro app may have.

The resetKeys also gets passed selectIsErrorFallbackRenderRequired(state) i.e.

From my state - calculate whether the app needs to render one of it's error UIs - and if this calculated value ever changes, reset i.e. render the ErrorBoundary's children instead of the fallback.

How I'm thinking about it is - this is my way in the ErrorFallback:

useErrorHandler(selectIsErrorFallbackRenderRequired(state));
Enter fullscreen mode Exit fullscreen mode

… and this is my way out:

resetKeys={[selectIsErrorFallbackRenderRequired(state)]}
Enter fullscreen mode Exit fullscreen mode

ErrorFallback gets passed state so it can do its own selections and render appropriately.

Seems to be working so far 🙂

Apart from separation of concerns, it has the added benefit of catching errors which React's error boundaries can catch (and default to the "contact support" error UI) - not that I'm expecting that to happen with tests and Typescript thrown in the mix 😛

Kudos to Kent C. Dodds and the OS community for another great tool 👏

Discussion (0)

pic
Editor guide