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 an 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-issuebranches in the Meteorite Explorer repo on GitHub.
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 the
try...catch statement in
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?
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
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
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).
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 in
SearchErrorBoundary.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
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.
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.
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).
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.
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
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.