1

I have a react/redux front-end that receives data via RabbitMQ. When a message arrives, our message handler will dispatch an action, e.g. PRODUCT_DETAILS_LOADED, that updates the store and renders the appropriate component with the product details. This is 95% working, but if I try to display nested object properties, I get an error: Cannot read property <prop> of undefined.

I have done some research on this. Most answers suggest that there's a race condition occurring, and that the component is trying to render the properties before they're "ready". I don't see how this can be the case, since I'm not making a REST call; I already have the data from the message queue and I've already updated the store. Moreover, a console.log inside the render function will show the correct values of the nested properties. Here's the bulk of my ProductDetails component:

const mapStateToProps = state => {
  return { product: state.productDetail };
}

class ProductDetails extends Component {

  render() {
    console.log(this.props.product.availability.forsale);  //This will correctly show 'true' or 'false'
    return (
            <div> 
                <h3>Product Details Page</h3>
                <h5>Details for {this.props.product.name}: </h5>
                <div>
                  Name:  {this.props.product.name}
                  <br/>
                  SKU:  {this.props.product.sku}
                  <br/>
                  Available:  {this.props.product.availability.forsale} //This throws an error
                  <br/> 
                  {/*Price: {this.props.product.price.$numberDecimal} //This would also throw an error
                  <br/>*/}
                  Description: {this.props.product.description}              
                </div>
                <Link to="/">Back to Home Page</Link> 
            </div>
    )
  }
}

const Details = connect(mapStateToProps, null)(ProductDetails);
export default Details;

Here's a snippet from the handler that fires when a product details message is received:

  } else if(json.fields.routingKey === 'product.request.get') {   
    if(json.status === 'success') {
     let payload = json.data;
      store.dispatch(productDetailsLoaded(payload));
    } 

Here's the function that gets dispatches the action:

export function productDetailsLoaded(details) {
  return { type: PRODUCT_DETAILS_LOADED, productDetail: details }
}

And finally, the reducer:

  if(action.type === PRODUCT_DETAILS_LOADED) {
    return Object.assign({}, state, {
      productDetail: action.productDetail
    });
  }

I've seen some answers that suggest something like the following to prevent the error: let av = this.props.product.availability ? this.props.product.availability.forsale : '';. This doesn't work for me. While it does prevent the error, I will display the blank value. This is not an option for me; I have to show correct information on the details page, not just "not-incorrect" information.

Can someone help me understand what's going on? Why does the console.log statement show the right data but the render statement immediately after bomb? And how can I fix this?

Thanks!

EDIT: Here's the object I'm trying to render:

{ 
    assets: {
        imgs: "https://www.zzzzzzz.com/content/12626?v=1551215311"
    }
    availability: {
        forsale: true, 
        expires: "2025-12-29T05:00:00.000Z"
    }
    createdAt: "2015-01-25T05:00:00.000Z"
    description: "his is a fake description"
    modifiedAt: "2019-05-28T04:00:00.000Z"
    name: "Product Name"
    price: {
        $numberDecimal: "59.95"
    }
    sku: "ZZZZ"
    "title ": "Product Title"
    _id: "5ceec82aa686a03bccfa67cc"
}
6
  • can you provide the sample object, that you are trying to render Commented Jun 13, 2019 at 13:38
  • What is the default state object of product? You are probably loading the component before redux has made any updates, and therefore none of the properties are available yet. Hard to tell without a bit more information though. There are a couple solutions, depending on the default state. Commented Jun 13, 2019 at 13:44
  • @DiveshPanwar I added the object. Commented Jun 13, 2019 at 13:49
  • @CalIrvine Help me understand, how does default state matter? There is no real default state for productDetail, just a dummy object with a "name" field. From where I sit, it looks like the state is being updated correctly; like I said, the console.log statement always shows complete & correct information. Commented Jun 13, 2019 at 13:51
  • If the component tries to load before the state has time to update (as redux state is asynchronous) then the object will not yet exist when the component is trying to access it's properties. Commented Jun 13, 2019 at 14:02

1 Answer 1

4

You are attempting to access the object before it is ready. The two places that you are having an error thrown are sub-properties. So for everything else, the initial value is "undefined" which your application is fine with, but once it tries to read a property of undefined, it will throw an error.

There are three solutions: 1) make sure the component does not load at all until the state has finished updating. This logic would be handled in the parent component of ProductDetails.

if 1) isn't an option, do one of the following:

2) set productDetails: null then in your component

render() {
    console.log(this.props.product.availability.forsale);  //This will correctly show 'true' or 'false'
    if (!this.props.product) return <div />;
    return (
            // what you have above
    )
  }
}

const Details = connect(mapStateToProps, null)(ProductDetails);
export default Details;

3) Set a default state shape, something along the lines of:

{ 
    assets: {
        imgs: null
    }
    availability: {
        forsale: null, 
        expires: null
    }
    createdAt: null
    description: null
    modifiedAt: null
    name: null
    price: {
        $numberDecimal: null
    }
    sku: null
    "title ": null
    _id: null
}

This is part of the default behaviour of react as much as it is a redux issue. See https://reactjs.org/docs/react-component.html#setstate for more on how setState works and why you should not treat it as synchronous code.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.