Exploit React Error Boundaries to Improve UX
How to Automate Error Detection & Reporting
User Experience (UX) is a broad and deeply complex discipline that is recognized by most in the development community as being integral to the success of an application. However, one aspect of UX that doesn’t get the attention it deserves in most applications centers around error detection and reporting.
Too many applications take a casual approach to error detection, handling, and reporting because developers view it as either drudgery or unimportant. It’s not just developers who are at fault. It is often the case that Product Managers prioritize new features over items like error handling and recovery that are viewed as mundane.
However, there are compelling reasons for giving attention not just to how application failures are detected and handled, but the UX considerations governing how they impact the user.
- Users expect applications to work - not to fail. In general, users tend to remember the failures and their impact more than the successes they achieve from the application. What will your users remember if they have to follow a procedure like this to report every issue?
- The best applications are those that are invisible to the user. The goal is to not get in the way of getting work done rather than having to deal with the mechanics of using the application.
- “Perception is reality.” How users perceive an application depends on the framing of information, events, and results. Sometimes developers are their own worst enemies when it comes to things like error messages. For example, “A catastrophic error has occurred! Open a ticket…” as opposed to “We’re unable to process your request at this time. For more information see…”.
- Software architecture is as much a feature as any application functionality supporting a particular business process. Anything that supports the applications ability to add value to the user should be considered as a feature.
There are a number of techniques and tools that can be used to make error detection and handling more transparent to the user and to improve the speed and quality of developer response. These include,
- Designing and implementing a overall error detection and handling strategy
- Crafting messages that are easy to understand and which are actionable
- Logging pertinent application data and user activity
- Automating recovery
- Automating screenshots for diagnostic purposes
- Automatically creating issue tickets for when errors are encountered
The remainder of this article will focus on two of these — implementing an application-wide error detection strategy and automatically creating issue tickets. We’ll be demonstrating these using the feature/06-error-handling
and the feature/07-create-issue
branches in the Meteorite Explorer repo on GitHub.
Implementing an Error Detection & Handling Strategy
Implementing an error handling strategy seems quite simple. We’ll start by wrapping the highest level React component in a try...catch
statement to intercept any errors.
To test we’ll throw a new Error in Search.js
when the user clicks on the ‘Search’ button. Testing this we see the error message ‘Unknown error intercepted…’ in the console. But wait! That’s not what is displayed. It looks as though the error didn’t percolate up to thetry...catch
statement in index.js
.
In the stack trace we can see the error was intercepted, but not by the applications index.js
routine. Instead, it was detected by development.js
in the react-dom
library and stopped there.
So, now we have a dilemma. How can our application code implement additional logging or automated recovery if it can’t get control when an error occurs?
React Error Boundary
Luckily, the Error Boundary feature was introduced in React 16. Error boundaries are components that intercept errors thrown during the rendering process, from within lifecycle methods, and inside constructors in the entire tree they encapsulate.
Figures 3 and 4 show the GlobalErrorBoundary
created for Meteorite Explorer and its use within App.js
. Notice that since this error boundary component is being used to intercept errors across the application, and as such it brackets all components. The result is it will receive control for error occurring anywhere within the component hierarchy.
Executing the application after adding GlobalErrorBoundary.js
will display the message “Meteorite Explorer encountered an error! Oh My!”. But once again the reality doesn’t match our expectations (see Figure 5, below).
The reason the expected message isn’t displaying is that the Meteorite Explorer application has been generated using Create React App, which injects its own error overlay into applications running in development mode. Any of the following three alternatives may be used to suppress this overlay,
- Run in production mode
- Click the ‘X’ in the upper right-hand corner of the overlay to dismiss it (see Figure 6)
- Use Chrome DevTools to remove the
error
andunhandledrejection
event listeners
As useful as Error Boundaries are they do have a few limitations. They cannot intercept errors originating in,
- Event handlers
- Asynchronous code (e.g., callbacks)
- Server-side rendering (SSR)
- Errors generated from within the error boundary itself
The Case for Multiple Error Boundaries
Although globally intercepting errors is one option, it’s even more valuable to set error boundaries around specific components (see Figure 7). Just like try...catch
statements, error boundaries can be used to improve error message quality by providing information specific to each situation and to tailor automatic recovery accordingly.
To demonstrate, the Search.js
component has been updated to generate an error if the user attempts to search on a prohibited word (see Figure 8) and the search components have been placed in their own Error Boundary. Errors trapped by specific error boundaries such as this do not percolate to higher error boundaries, even though they may be nested. However, it’s still a good practice to use a global error boundary to catch any errors outside the scope of an error boundary at a lower level.
When a prohibited word is detected not only will an error be thrown, but the SearchErrorBoundary
component will also create a customized message displaying the specific word that triggered the error (see Figure 9).
Automating Issue Ticket Creation
The examples in the previous section were created to demonstrate the basic functionality of a React error boundary. They are, however, not very practical since they trap a particular condition and generate a custom error message. If this is all that is required, it can be better accomplished using more straightforward techniques.
Intercepting errors, gathering information defining the state of the application at the time it occurred, and automatically opening a support ticket is a feature valuable to both users and developers. For users, it removes the burden of manually creating a new ticket, and for developers, it improves the type and quality of information needed to resolve the problem.
To show how this might be accomplished using React Error Boundaries the componentDidCatch
function inSearchErrorBoundary.js
has been updated to create a new issue ticket from the data available at the time the error was detected. This includes props containing the current search term that triggered the error. A new GitHub issue is created by the call to createIssue(),
and a JSON string representing the issue is returned and added to SearchErrorBoundary
's state.
The render
function generates a customized error message including the link to the new issue in the Meteorite Explorer repo on GitHub.
The createIssue
utility function relies on the GitHub Create Issue package to create new issues. This package uses the GitHub REST API V3 and was selected because although GitHub’s GraphQL API V4 supports the creation of issues, it is currently in preview mode and subject to change at any time.
The formatGHIssue
function creates a Markdown formatted issue body and uses the GitHub Create Issue package to create a new issue. Upon completion, the JSON representation of the new issue is returned to the caller.
The formatGHIssue
function creates an issue body containing information helpful to the developer such as the identity of the user who experienced the error, environmental data such as the client platform, the error message, and the stack trace. Also, it also adds a label identifying the issue as being a bug.
With these changes in place, when an error is intercepted an issue reporting the error is created in GitHub (see Figure 13) and a customized error message is displayed (see Figure 14).
Making it Production-Ready
As useful as this may seem it’s not production-ready. It’s merely a demonstration of how React Error Boundaries can be used to create a useful application feature. A few additional items to consider to make it production-ready are,
- Limiting the number of issues automatically generated in a predefined time frame to prevent duplicates and flooding the ticketing system.
- Searching for existing issues before creating a new one.
- Ensuring that errors in the issue creation logic don’t result in a recursive loop.
- Adding user profile data to the ticket as long as it doesn’t contain personal identifying or confidential information.
- Allowing the user to provide supplemental information as part of an automatically created ticket. For example, the steps to recreate the problem.
- Your organization may dictate a ticketing system such as ServiceNow, Remedy, Bugzilla, or Jira rather than GitHub.
- Since different Error Boundary handlers have a lot in common an opportunity exists to create a generic handler that’s customized using props and state.
Guarding Application Secrets
It’s worth noting that to keep client secrets, like Meteorite Explorer’s GitHub access token, confidential they are defined as environment variables in the .env
file. This file is added to .gitignore
so it won't be uploaded to the public GitHub repo where it would be visible to anyone.
In applications created by Create React App, these environment variables are required to be prefixed by REACT_APP_
. These can be accessed within application code using variables such as process.env.REACT_APP_GITHUB_ACCESS_TOKEN
.
Wrapping It Up
Creating leading-edge applications is a very complex and demanding task. As organizations mature their core processes often increase in sophistication which requires the use of equally sophisticated applications. Although domain-specific features are important, sometimes the largest gains in user satisfaction come from correcting seemingly small issues and adding functionality to help make the application “invisible”.
“The difference between something good and something great is attention to detail.” … Charles R. Swindoll
Sometimes the best path to success comes from taking the time to listen to users, observe how they use the applications they depend on, paying attention to improving mundane and repetitive tasks, and eliminating even the smallest of obstacles to productivity. In application development attention to even the smallest of details matters.
What is Chingu?
Come join us at Chingu.io to get out of “Tutorial Purgatory” by joining one of our remote Voyage project teams to build apps, refine “hard” skills, and learn new “soft” skills. We help Developers bridge the gap between what they’ve learned and the skills employers are looking for.