When rendering a component in React (with many subcomponents) and a JS error is thrown for whatever reason, what\'s the best way to handle this? Sure I can catch the error
React 16 introduces a new concept called “error boundary” to handle errors occur inside React components without breaking the whole app.
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 that crashed.
Error Boundary components have been made possible with a new life cycle method componentDidCatch(error, info)
. Not like other life cycle methods, this will get called only if any error occurred in during rendering, in lifecycle methods, or constructors of any child (including all grandchildren) component of the component.
In code, Error Boundary component will be something like follows.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
// Display fallback UI
this.setState({ hasError: true });
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Error occurred!</h1>;
}
return this.props.children;
}
}
We can use this Error Boundary component as a normal component in our code.
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
Now if MyComponent throws any JavaScript error in during rendering, in lifecycle method, or in constructing, componentDidCatch
of Error Boundary component will trigger and change the state to show Error occurred!
message instead of the broken MyComponent.
This new functionality comes with another vital implication that you wanted to know before migrating to React 16. Previously, if an error occurs, it leaves corrupted UI even though it doesn't usually work as expected until you reload the page. However, in React 16, if an error hasn't handled by any error boundary, it will result in unmounting of the whole app.
Because of this, you are adding error boundaries to your app appropriately will give a better experience to your user. So users will be able to interact with a part of your app even though some areas of UI have crashed.
Refer React official documentation on error boundaries or this official blog post for more information. They cover almost everything you need to know about this new feature.
Ideally you shouldn't ever have something that would cause a js error. We don't live in a perfect world though so heres a few things I would do to help mitigate them.
Think about how the code you're writing could break
if you are calling a method on a variable, think, "is this always going to be this datatype?"
handleClick = (e) => {
e && e.preventDefault()
// code here
}
handleSomething = (data) => {
if (Array.isArray(data)) {
data.reduce(...) // we know this will be here
}
}
When making variables, use default values.
Meaning const { loading = false, data = [] } = this.props
. This will help in data type consistencies.
Handle asynchronous data more elegantly
Make sure to handle the pending / waiting state of your component. You could (for instance) use a Loading
component that renders the content when loaded.
render() {
const { data = [] } = this.props
return (
<Loading loading={data.length > 0}>
<MyComponent data={data} />
</Loading>
)
}
Where the loading component would be something like.
const Loading = ({ children, loading = false, message = 'Loading...' }) => {
if (loading) {
return <span>{message}</span>
}
return children
}
You should catch exceptions
If you're writing something that you think could break or if you just want to be overly cautious you can use try catch
blocks to catch exceptions in functional components.
const MyComponent = ({ data = [] }) => {
try {
return <ul>{data.map( item => <li key={item}>{item}</li> )</ul>
} catch (error) {
const message = error.message || 'I broke!'
return <span className="broken-component">{message}</span>
}
}
In class components you can use
componentDidCatch(error, errorInfo) {
// Handle error here.
}
The docs have a ton of great info if you'd like to learn more
Now, this is just a rudimentary example to describe what I am talking about. But it will help reduce your JS exceptions, which (when uncaught) will break your single page application. So you need to handle them.
If a URL is invalid redirect to a 404 page. If a component needs data then wait for the data to render it. If you are trying to access a property on an object that is nested (especially if its from the server), aka this.props.myData.obj.something.somethingelse
. Chances are that that object path wont always be there. You need to ensure that each of those aren't undefined or null.
Extra credit
I've used lodash's get
helper, which has helped me reduce exceptions!
_.get(this.props, 'myData.obj.something.somethingelse') // returns `undefined` if the path is invalid
I guess the moral of the story is you should be proactive in your code to capture things that could break. :)
A JavaScript error in a part of the UI shouldn’t break the whole app. To solve this problem for React users, React 16 introduces a new concept of an “error boundary”.
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 that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.
A class component becomes an error boundary if it defines a new lifecycle method called componentDidCatch(error, info)
ref link
For React v16 and up, the recommended way is to use error boundaries. This page (short and good)from the React team explains the feature: https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html. It includes a link to a codepen demo as well.