Knowledge Hub
As much as we all hate these moments they are an inseparable part of the developers' work. While we are used to it during the development process, what is the best practice if the error occurs when your application has been deployed?
Should you inform the user about the error? Or should you hide it from him to keep the impression that the application is spotless?
We all have our own answer to it and I’m not gonna go into this. Instead, I’d like to share with you the ways that you can react to this. And maybe nudge you in the right direction.
The development process is full of issues, errors, problems that you deal with every day. It might be a small typo or a wrong usage of React hooks. You know general mistakes that you can easily solve by yourself.
Modern React applications more often depend on external sources, like RESTful APIs. Those services help you save time and money. You can write more complicated front-end apps without a line of a backend. This approach has a lot of pros, but unfortunately also cons with the biggest one being no influence to the source.
What happens if the API you are using changed a very small part and this change becomes incompatible with your deployed application? If you don’t monitor your deployed app most probably you will not notice there is an issue at all. But chances are you’ll notice that fewer users visit your app.
Now, you can report an issue to the API authors, but it will take some time before they fix it or help you rebuild your solution. Should your application wait for the external fix and be unavailable till then? Of course not!
There are multiple options on how to protect your application from failure. But most importantly you have to have control over the errors, even the external ones. Basically, you always have to be one step ahead of the potential threat.
So, what can you do?
The first error "firewall" is utilizing unit tests. Most devs who have not started writing/using unit tests yet think that this is just a waste of time. In case you are in this group just ask one of the devs that are already into it.
Writing unit tests helps not only eliminate the bunch of errors in the early stage of the application, but it helps you to predict them as well. This means that you should write tests before coding, test something that doesn't exist yet. While it sounds weird, this approach actually forces you to consider multiple aspects of the problem beforehand. During this inverted process of coding, you will notice edge cases that you probably would not spot if you were to start from a solution to tests.
With JavaScript we have multiple options that can help you protect your application from failing.
In case that function manipulates the passed arguments when the argument is null or undefined and there is nothing to manipulate, or you have asynchronous callbacks with an error parameter, very basic check conditions can save you from throwing the application.
function (props, error) {
if(error) {
return "Ooops.."
}
/* function actions */
}
The MDN definition says: "The try...catch statement marks a block of statements to try and specifies a response should an exception be thrown."
This block is very common to use an asynchronous function calls. The main reason to use this block is that error handling depends on the catch implementation while the rest of the code can be executed and the application will not break.
async function myAsyncFunction() {
// some promise
}
function callAsyncFunction() {
try {
const request = await myAsyncFunction()
const response = request.json()
return response
} catch (error) {
console.error(error)
}
}
The JavaScript beginners have contact with throw when the application returns a big scary red error message in a console.
What is throw? It is a statement that returns a "user-defined" exception. It can be a string, an integer, or an error message.
function isString(value) {
if (typeof value !== 'string') {
throw new Error('The value is not of type string')
} else {
return true
}
}
try {
isString(1)
} catch (error) {
console.error(error)
// output: "The value is not of type string" + call stack information
}
What are the pros? When the application encounters the throw statement it stops propagating the further code and returns the throw message. It is helpful for many reasons, but the most important one is that if the application comes across the problem, the throw statement stops the next actions.
Imagine that you are working on the submission form. During the submission process, the error occurs, but despite this, the form data (most probably wrong data) has been submitted. You don't want this to happen and stopping the application in the proper moment is the best solution.
What's more, the throw statement returns a call stack that provides you with much useful information about the cause of the failure.
Thanks to that information you can track where the real bug occurs.
On the other hand, not every error should stop your application from running the content for the user. This is where ErrorBoundary helps!
All of the above-mentioned possibilities of handling errors are important and you should be familiar with them. The risky part is that some of them can stop React components from rendering. In those situations, the user doesn't get the feedback from your site.
Unfortunately, most of your users don't know about developer tools' existence either. If they see that your page is not working, they just leave and never come back. What should you do? Return feedback to the user that unpredictable error occurs but you are handling it.
React came up with a very simple idea for this problem, ErrorBoundary, which is just a component that catches JavaScript errors anywhere in their child component tree, logs those errors, and displays a fallback UI instead of the component tree that crashed (source: React docs).
When the React catches an error and the component is wrapped with the ErrorBoundary component, it just unmounts it from a tree, returns a fallback you implement in the ErrorBoundary component, and renders other components on your page.
Even if the error occurs, your page will still be working for the user. Woohoo!
Add a new component named ErrorBoundary that can be used anywhere in your App tree. You don't need any external library for it, just React.
class ErrorBoundary extends Component {
constructor(props) {
super(props)
this.state = { hasError: false }
}
componentDidCatch() {
this.setState({ hasError: true })
}
render() {
if (this.state.hasError) {
return <span>Oops.. something's went wrong.</span>
} else {
// when there's not an error, render children untouched
return this.props.children
}
}
}
export default ErrorBoundary
Wrap a component for which you want to return feedback to the user if an error occurs inside of the component and that’s all there is to it.
<ErrorBoundary>
<MyComponent />
<ErrorBoundary>
You can reuse the component as many times as you like. What's more, thanks to this you can get feedback from the users as well. How? By adding a form in the ErrorBoundary to send a report about the bug. This would make your debugging process much easier and quicker.
But what if the user is not willing to report an error?
If you want to be notified in real-time when an error occurs, try using one of many error monitoring software on the market. They are built to help you discover bugs even before the user notices it.
Most of the monitoring software provides metrics and analytics, so you could check out in which segment your code needs more attention. Furthermore, if a user doesn't report an error with the form provided with the ErrorBoundary component, you will be notified anyway. You can also use the reporting functionality provided by the software. There are few systems that work with the React apps, e.g. Sentry.io, Bugsnag, or Airbrake. I recommend trying them all to find the best option for your project.
Handling errors or pretending that your application is clear and perfect?
From my perspective, a few years of working as a developer taught me that pure applications don't exist. Pretending that your application will not throw any error because you test it completely is a lie. Why?
Because users are unpredictable. There is a very small chance that you’ll discover every scenario for your project and user behavior. What's more, ignoring small errors during the development process can lead to harmful bugs in the future.
Handle your errors before they happen.
Happy coding!