EXPEDIA GROUP TECHNOLOGY - SOFTWARE

Improve Customer Experience via Error Boundaries

Handling React errors gracefully with error boundaries

Abhishek Jakhar
Expedia Group Technology

--

A lodge house on a snowy hill
Photo by Wes English. Used by permission.

In an ideal world, our app renders perfectly every single time. Unfortunately, problems can occur in our codebase and even crash our app. When our application crashes, it renders a blank white screen.

React encourages thinking in terms of components. Having multiple smaller components is better than having a single giant component. This is what we do here on the Vrbo™️ Stay Experience team (Vrbo is part of Expedia Group™️). We have multiple components like ModifyBooking, CancelBooking, TripSummary, and CancellationSummary. They form our Trip Details page when combined together.

Recently, we faced an issue on Trip Details where the page went completely blank in production. We investigated and saw that we had missed a null check in our CancellationSummary component. This issue could put our customers in a very uncomfortable position:

  1. The customer could be at the airport trying to find the address and directions to the property.
  2. The customer is at the property after a long flight or drive and wants to get in ASAP to take some rest, but cannot because the access instructions are not visible.
  3. The customer wants to contact the property owner before visiting the property.
  4. The customer wants to modify or cancel the booking because of a sudden change in plans.
  5. The customer wants to see the places and activities near them.
  6. The customer wants to check amenities instructions to use the washing machine.

Why should our customers suffer because of one failing component? Wouldn’t it be nice if we could contain any error within that component, so the rest of our page keeps working as normal? Wouldn’t it be great to show a fallback UI or hide the component entirely when it fails?

React 16 introduces the error boundary concept. It is a component which catches errors, logs those errors and displays a fallback UI instead of crashing other components. We decided to implement it in our application. Afterwards, we were able to provide a better experience to our customers because our page is now visible to the user even if there is an error in some part of it.

Error boundaries

Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree crashing. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them. Instead of showing a fallback UI we can also hide the component if it has an error.

We have a React application which renders two components, <Greeting/> and <Farewell/>. The <Greeting/> component takes in name of person and greets them. On the other side the <Farewell/> component takes in name of person bid them farewell. Now, an error is introduced in the Greeting component, because we forgot to pass name prop (as a string) or default the name prop’s value, due to which the toUpperCase() function is called on undefined.

App.js

What happens if we ship this code to our users?

Left: Production environment, Right: Development environment

In the example shown above you see that the <Farewell/> component received the name prop so it should have worked fine, but because of an error in the <Greeting/> component our complete tree crashed. We will wrap both the <Greeting/> and <Farewell/> components in ErrorBoundary components and observe the runtime behaviour of our application.

<Greeting /> & <Farewell /> component wrapped in <ErrorBoundary />

Now, if there is any error inside the <Greeting/> component it will not break our entire UI, our <Farewell/> component will remain intact. In the image below we show the fallback UI of “Something went wrong” in place of the<Greeting/> component which is not visible.

Fallback UI in place of greeting component
Fallback UI in place of <Greeting /> component

When to show fallback UI vs hiding the component

Critical components are very important for our users. Here are some examples.

  1. TripSummary — To see the details of your reservation
  2. Amenities — To check which amenities are available at the property
  3. ModifyBooking — To modify the reservation
  4. CancelBooking — To cancel the reservation
  5. Message — To send a message to the property host
  6. Quote — To check payment status

A user may specifically come to the Trip Detail page just to use one of them. Thus we cannot hide them.

Imagine a user needs to modify their booking. They come to the page searching for some booking modification UI, but they can find no option for modifying a booking. Instead, it would be a better user experience to show a fallback UI in the case that this critical component has failed to load for some reason. If there was a fallback UI, then the user would see that they are in the right place but there is an error and they should try again in few minutes.

Noncritical Components are the components which are add-ons to provide extra value to users. Examples of noncritical components are in the list below.

  1. Weather — To see current weather conditions at destination
  2. Adverts — To see activities available in the nearby area of property

It less likely that users come to the page looking for noncritical components. It is perfectly fine to hide them if they have an error, because showing an error on the screen is also a bad user experience.

Guest of guests and error boundaries

We decided to implement Error Boundaries for guests of guests (GoG) because

  1. We were working on implementing the Atomic design pattern on the GoG component and made lots of changes to its codebase. From a safety perspective, we thought that wrapping it inside an error boundary would be sensible.
  2. There were no dependencies on product and design, we could just hide this non critical component.

Implementing an error boundary in GoG

  1. We installed an in-house error boundary component which also provides Datadog integration.
  2. We created our own error boundary wrapper over the in-house error boundary. We did this so we could have more control over our error boundary, like logging to a custom service, styling the fallback UI, showing the fallback UI, or hiding the component in case of error.
  3. We created a generic fallback component called ErrorPanel which can be seen in the code snippet given below.
  4. We integrated a custom logging service. With the help of window.Sentry.captureException(error, {extra: errorInfo}) we were able to log detailed errors to our Sentry dashboard.
  5. We integrated PagerDuty with Sentry and Datadog and connected PagerDuty to Slack for notifications.

Wrapping of GoG in our error boundary higher order component (HOC):

wrapping <GuestsOfGuest/> in withErrorBoundary HOC

Error Boundary HOC implementation which internally uses ErrorReportBoundary and also logs data to Sentry when error occurs:

withErrorBoundary higher order component implementation

Benefits of error boundaries

As we integrated withErrorBoundary into GoG we saw spikes in Datadog, then we quickly checked the logs of Sentry and found out that there was an issue in the GoG component. The error message said ‘URLSearchParams’ is undefined; on further debugging we found that the ‘URLSearchParams’ API used in the updated GoG component is not supported by Internet Explorer (IE) 11. This issue had existed for two months but we were not aware of it. It caused not only our GoG component to break but our entire trip details page had been breaking on IE 11. After the introduction of ErrorBoundary, we were able to detect the error. Instead of breaking the whole page, we started hiding the GoG component and the rest of the page was working fine. We finally fixed the issue by using the polyfill for URLSearchParams.

Below are the images that helped us debug the IE 11 issue:

Datadog monitor for Guest of Guests widget which shows the error count for Guest of Guests widget
Datadog monitor for GoG which shows the error count for GoG widget
Sentry dashboard showing the detailed information about error in GoG component
Sentry dashboard showing the detailed information about error in GoG component
This is how trip details page looks when GoG component is wrapped inside an Error Boundary
This is how trip details page looks when GoG component is wrapped inside an error boundary.

If you are not already using error boundaries in your React application, it’s generally advisable to use at least one error boundary at the root of your application. This will prevent users from seeing a blank page, and perhaps see a relevant fallback UI.

Note: If you want to go the extra mile you can make the error boundary HOC more reusable by making different types of fallback UI’s. You can also integrate logging services like Sentry and Datadog.

References

https://reactjs.org/docs/error-boundaries.html

--

--