Error Boundaries


In this article We will understand about a Concept in React called as Error Boundaries.

To understand things better, I am accessing our react application which we have developed in our last session from the browser. This is the Product Ordering screen which we have developed and it has three sections and they are Product Information section, Address Section and Summary Section. Now our new requirement is that in the Address Section, we have to display the Current user’s Preferred Address list which user has entered previously. 

We will create a New Class called as UserPreferredAddressList component class and we will implement render method.

class UserPreferredAddressList extends React.Component{

  constructor(props){

    super(props);

  }

  render(){

      return (

      <div>

        <h2>Your Existing Addresses...</h2>

        <p>

          Office<br></br>

          Marathahalli, Bangalore-560037

        </p>

      </div>     

    );

  }

}

Lets Call this Component from our Address Component Class. Lets save these changes. Navigate to the browser. We can see that Preferred Address List is displayed in the Address Section.

Now assuming that we are having some problems to load the User Preferred Address List and that code is throwing us an error. Then what we expect the application to do is it should show us a message that we are not able to Load your preferrences. Please Enter the Address and Continue Ordering the Product. 

But that will not happen by default. 

Lets go to UserPreferredAddressList class and throw an error from the rendered method. 

throw new Error("Not able to Fetch the Addresses at this moment");

Lets save this Change. Navigate to the browser. We can see that Error is displayed and we can see the Complete Error Stack in the browser.

Now how do we handle this error and show the Custom message to the user that we are not able to load the Preferred Address List now and please Enter the Address and Continue placing your Order.

This is where we will make use of Error Boundaries in React.

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 either (or both) of the lifecycle methods static getDerivedStateFromError() or componentDidCatch()

We Use static getDerivedStateFromError() to render a fallback UI after an error has been thrown. 

We Use componentDidCatch() to log error information.

Lets see how we can create Error Boundary. We will create a Component Class and extend it from React.Component Class. Lets add Constructor and make a call to base class constructor.

Lets create a state object and add a property called hasError to this object. Lets implement both life cycle methods.

One method is static getDerivedStateFromError, we use this method to update our state object so that the next render will display the Custom UI instead of the error.

The second method is componentDidCatch, we can use this method to log the error details either to Console or to any other service. This componentDidCatch accepts two arguments. One is the error object which contains the error information and the other one is errorInfo which contains the component stack. That component stack gives us parent component details from where the call is made to the respective component.

Now lets implement render method. In render method, we will check our hasError property and if it has the error, we will return a div which will display the custom error message. If there is no error, we will return its children as is.

class CustomErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.log(error);
    console.log(errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // Error path
      return (
        <div>
          <h2>We are having Problems to Load your Preferred Addresses. Please Select...</h2>
          
        </div>
      );
    }
    // Normally, just render children
    return this.props.children;
  }
}

Now lets call our UserPreferredAddressList Component with in the boundaries of this Error Boundary Class we have created.

Save these changes. Navigate to the browser. We can see that we get the Custom Error is displayed for us and the rest of the application works as it is. We have logged our error and errorInfo objects to the Console window. Lets open the developer tools, navigate to the console tab and we can see that both error and errorInfo object values. As we can see that error displays the error information and errorInfo shows us the component stack. It shows us that this error occurred from the Order Component as that is our Parent Component.

Error boundaries do not catch errors inside event handlers.

React doesn’t need error boundaries to recover from errors in event handlers. Unlike the render method and lifecycle methods, the event handlers don’t happen during rendering. So if they throw, React still knows what to display on the screen.

If you need to catch an error inside event handler, use the regular JavaScript try / catch statement

Error boundaries do not catch errors also for:

Any Asynchronous code we write (e.g. setTimeout )

For Server side rendering code

For Errors thrown in the error boundary component class itself (rather than its children)

Error boundaries works like a JavaScript catch {} block, but for components. Only class components can be error boundaries. 

The granularity of error boundaries is up to us. We may wrap top-level component with in the scope of Error boundary or We may wrap a specific component with in the scope of Error boundary.

import React from 'react';

import ReactDOM from 'react-dom';



class OrderComponent extends React.Component{

  constructor(props){

    super(props);

    this.state={quantity:'',address:''};

  } 



  orderInfoChanged=val=>{

    this.setState({quantity:val});

  }



  addressChanged=val=>{

    this.setState({address:val});

  }



  render(){

    return(

      <div>

        <h1>Product Order Screen...</h1>

        <ProductInformationComponent quantity={this.state.quantity}

                            onQuantityChange={this.orderInfoChanged}></ProductInformationComponent>



        <AddressComponent address={this.state.address} 

                      onAddressChange={this.addressChanged}></AddressComponent>

    

        <SummaryComponent quantity={this.state.quantity} address={this.state.address} 

                      onQuantityChange={this.orderInfoChanged}></SummaryComponent>

      </div>

    );

  }

}



class ProductInformationComponent extends React.Component{

  constructor(props){

    super(props);

  }



  handleChange=e=>{

    this.props.onQuantityChange(e.target.value);

  }

  render(){

    return (

      <div style={{border:'3px solid red'}}>

       <h2>Product Information...</h2>

       <p>

         <label>Product Name : 

           <select>

             <option value="Product-1">Product-1</option>

             <option value="Product-2">Product-2</option>

             <option value="Product-3">Product-3</option>

           </select> </label>

       </p>

       <p>

         <label>Enter Quantity : <input type="text" value={this.props.quantity}

                                        onChange={this.handleChange} ></input></label>

       </p>

     </div>

    );

    

  }

}



class AddressComponent extends React.Component{

  constructor(props){

    super(props);

  }



  handleChange=e=>{

    this.props.onAddressChange(e.target.value);

  };



  render(){

    return (

      <div style={{border:'3px solid red'}}>

        <h2>Address Information...</h2>

        <p>

          <label>Address : <textarea value={this.props.address} 

                            onChange={this.handleChange}></textarea></label>

        </p>

        

        <CustomErrorBoundary>

        <UserPreferredAddressList/>        

        </CustomErrorBoundary>

        

        

      </div>

    );

  }

}

class CustomErrorBoundary extends React.Component {

  constructor(props) {

    super(props);

    this.state = { hasError: null };

  }



  static getDerivedStateFromError(error) {

    return { hasError: true };

  }



  componentDidCatch(error, errorInfo) {

    console.log(error);

    console.log(errorInfo);

  }



  render() {

    if (this.state.hasError) {

      // Error path

      return (

        <div>

          <h2>We are having Problems to Load your Preferred Addresses. Please Select...</h2>

          

        </div>

      );

    }

    // Normally, just render children

    return this.props.children;

  }

}

class UserPreferredAddressList extends React.Component{

  constructor(props){

    super(props);

  }

  render(){

    throw new Error("Not able to Fetch the Addresses at this moment");

      return (

      <div>

        <h2>Your Existing Addresses...</h2>

        <p>

          Office<br></br>

          Marathahalli, Bangalore-560037

        </p>

      </div>     

    );

  }

}

class SummaryComponent extends React.Component{

  constructor(props){

    super(props);

  }

  handleChange=e=>{

    this.props.onQuantityChange(e.target.value);

  }



  render(){

    return (

      <div style={{border:'3px solid red'}}>

        <h2>Summary Information...</h2>

        <p>

          <label>Product Name : <b>Product - 1</b></label>

        </p>

        <p>

         <label>Enter Quantity : <input type="text" value={this.props.quantity}

                                        onChange={this.handleChange} ></input></label>

       </p>

       <p>

         <label>Address : <b>{this.props.address}</b></label>

       </p>

       <button>Place Order</button>

      </div>

    )

  }

}

const element=<OrderComponent></OrderComponent>

ReactDOM.render(element,document.getElementById("root"));

Video Reference





© 2020 Pragimtech. All Rights Reserved.