Errors when developing are something that you will always have and it's better to know that i know how to handle them. In this post I'm going to write about how to handle them in React.
Try/Catch
I will start talking about try/catch method which is really simple but useful at the same time.
We just can use it for imperative code and event handlers which are often used in a declarative way in react.
Event handler using Try/Catch
The provided code demonstrates a basic event handler that employs a try/catch block. Within the try block, there is a comment indicating the potential presence of code performing operations like making API calls using fetch or internal API functions. The try block attempts to execute these operations, while the catch block is responsible for capturing any encountered errors in case the operations within the try block fail.
const MyComponent = () => {
const [error, setError] = useState(null)
handleClick() {
try {
// Do something that could throw
} catch (error) {
setError(error);
}
}
if (error) {
return <h1>Caught an error.</h1>
}
return <button onClick={this.handleClick}>Click Me</button>
}
Common mistakes
When utilizing the try/catch method, it is essential to be mindful of certain considerations. As this method operates in an imperative manner, it can lead to complications when employed alongside React lifecycle methods like useEffect, children components, and state updates during rendering.
Example with useEffect (Wrong)
This will never work, since we can't wrap useEffect with try/catch.
try{
useEffect(() => {
throw new Error("Main Error!")
},[])
}catch(e){
// useEffect throws, but this will never be called
}
Example with useEffect (Right)
This is an example of how to use try/catch with useEffect the right way.
useEffect(() => {
try{
throw new Error("Main Error!")
}catch(e){
// this one will be caught
}
}, [])
Example with children components
We also can not do this cause it will be useless cause it will never catch an error since the child component belongs to the lyfecycle method of the react virutal DOM.
const Component = () => {
try {
return <Child />
} catch(e) {
// still useless for catching errors inside Child component, won't be triggered
}
}
Example setting state during render
The following example will just cause an infinite loop in case of an error.
const Component = () => {
const [error, setError] = useState(false);
try{
something();
}catch(e){
setError(e)
}
}
We could use something like this though, where we will set the state inside a useEffect but we will also have a try/catch at the render but in case of error it will return an error screen instead of setting some state.
const Component = () => {
const [error, setError] = useState(false);
useEffect(() => {
try{
//something
}catch(e){
setError(true);
}
},[])
try{
something();
}catch(e){
return <ErrorScreen/>
}
if(error) return <ErrorScreen/>
return <NiceComponent/>
}
React ErrorBoundary component
To be able to avoid the limitations of try/catch we could use a "Error boundaries" Api that react give us that converts a regular component into a try/catch, only for React declarative code.
How to implement it?
Here is the way react documentation show us how to implement it.
const Component = () => {
return(
<ErrorBoundary>
<ChildComponent/>
</ErrorBoundary>
)
}
If something goes wrong in the child component or their children during render, the error will be caught and dealt with.
The catch is that react docs does not give us the component already built, but it gives us a tool to implement it.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
// initialize the error state
this.state = { hasError: false };
}
// if an error happened, set the state to true
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
// if error happened, return a fallback component
if (this.state.hasError) {
return <>Oh no! Epic fail!</>
}
return this.props.children;
}
}
And no, react does not support error boundaries built as a functional component yet.
So let me break down the code for you and explain it a little but is not that hard to understand if you have some experience with react.
Basically we extend react component, then we initialise a constructor that will have a hasError as the state, getDerivatedStateFromError was literally mainly build to be able to catch errors on the lyfecycle of react, so if it finds an error it will change the value of hasError to true and if hasError is true our errorBoundary will return "Oh no! Epic fail!" but in case it does not have an error it will just return the children the component has wrap it.
Add a fallback to the errorBoundary
Let's say we want to add a specific fallback depending what components had an error we could add a fallback to the errorBoundary so we will be able to return that specific component.
ErrorBoundary
render() {
// if error happened, return a fallback component
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
And this is how we would use it
const Component = () => {
return (
<ErrorBoundary fallback={<>Oh no! Do something!</>}>
<SomeChildComponent />
</ErrorBoundary>
)
}
Conclusion
We always would need to use try/catch to catch async functions and imperative code but the best would be to use a combination of both, Error Boundaries and try/catch to have a well documented code that handles errors right.
Hope someone found it useFul, and if you have any questions i would try to solve them, catch(e){Or not}.
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.