Ricky
Ricky

Reputation: 229

Why does specifying the prop's object's attributes does not work?

I had already found a solution by luck so I don't quite understand how it worked even after trying to read stuff online.

I am simply trying to get the array of comments inside the selectedDish prop. The selectedDish's state is populated inside the Main Component's function, and I am trying to access selectedDish's array of comments inside the DishDetail component.

I was able to get the comments by:

{ this.props.selectedDish && this.renderComments(this.props.selectedDish.comments)}

But I am unable to when I just do the following and receive this error "Cannot read property 'comments' of undefined":

{ this.renderComments(this.props.selectedDish.comments)}

Why does this work? Shouldn't this.props.selectedDish.comments be enough as I am able to successfully render the "renderDish(dish)" function?

Main Component

import React, { Component } from 'react';
import { Navbar, NavbarBrand } from 'reactstrap';
import Menu from './MenuComponent';
import Dishdetail from './DishdetailComponent';
import { DISHES } from '../shared/dishes';

class Main extends React.Component {

  constructor() {
    super();
    this.state = {
        dishes: DISHES,
        selectedDish: null
    };
   // this.onDishSelect = this.onDishSelect.bind(this);
  }

  onDishSelect(dishId) {
    this.setState({
       selectedDish: dishId
    });
  }

  render() {
    return (
      <div>
        <Navbar dark color="primary">
          <div className="container">
            <NavbarBrand href="/">Ristorante Con Fusion</NavbarBrand>
          </div>
        </Navbar>

        <Menu dishes={this.state.dishes} onClick={(dishId) => this.onDishSelect(dishId)} />
        <Dishdetail selectedDish={this.state.dishes.filter((dish) => dish.id === this.state.selectedDish)[0]} />
      </div>
    );
  }
}

export default Main;

DishDetail Component

import React, { Component } from 'react';
import { Card, CardImg, CardText, CardBody, CardTitle } from 'reactstrap';

export default class Dishdetail extends React.Component {

    constructor(props) {
        super(props);
    }

    renderDish(dish) {

        if (dish != null)
            return (
                <Card >
                    <CardImg width="100%" src={dish.image} alt={dish.name} />
                    <CardBody>
                        <CardTitle>{dish.name}</CardTitle>
                        <CardText>{dish.description}</CardText>
                    </CardBody>
                </Card>
            );
        else
            return (
                <div></div>
            );
    }

    renderComments(comments) {
        let list = (<div></div>);
        if (comments != null) {
            list = (
                <ul className="list-unstyled">
                    {comments.map(c => {
                        return (
                            <li key={c.id}>
                                <p>{c.comment}</p>
                                <p>-- {c.author}, {new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'short', day: '2-digit' }).format(new Date(Date.parse(c.date)))}</p>
                            </li>
                        );
                    })}
                </ul>
            );
        }
        return (
            <div>
                <h4>Comments</h4>
                {list}
            </div>
        );

    }

    render() {

        return (
            <div className="row">

                <div className="col-12 col-md-5 m-1">
                    {this.renderDish(this.props.selectedDish)}
                </div>

                <div className="col-12 col-md-5 m-1">
                    {/* This works: */}
                    { this.props.selectedDish && this.renderComments(this.props.selectedDish.comments)}

                    {/* This doesn't:  */}
                    {/* { this.renderComments(this.props.selectedDish.comments)} */}
                </div>
            </div>
        );
    }
}

Upvotes: 1

Views: 289

Answers (3)

dorriz
dorriz

Reputation: 2679

Just a few pointers , you don't need to have a class based component since you aren't using lifecycle methods etc. you can use destructuring to reduce typing let {selectedDish:{comments}} = this.props .

You could solve your problem with a default value for comments ,

`const Dishdetail = ({selectedDishes:{comments}}=[]}) => {}  

and conditional rendering in render

<div>
{ comments.length > 0 &&
   //map you comments here if you have some

}
</div>`

Upvotes: 1

Mohit Mutha
Mohit Mutha

Reputation: 3001

When your component is loaded the first time the selectedDish state value is null. It only gets set later, hence the error.

Changing the below code

constructor() {
    super();
    this.state = {
        dishes: DISHES,
        selectedDish: null
    };
   // this.onDishSelect = this.onDishSelect.bind(this);
  }

To

constructor() {
    super();
    this.state = {
        dishes: DISHES,
        selectedDish: {}
    };
   // this.onDishSelect = this.onDishSelect.bind(this);
  }

will make it work. However I feel that what you are doing now i.e. null check and then render is a more robust way of doing it.

Upvotes: 1

kooskoos
kooskoos

Reputation: 4869

When your main component renders for the first time the props are still undefined as DISHES haven't been loaded, reasons for that might be the DISHES are initiated with some external resource.

So when this.props.selectedDish is undefined you can't access it's key because they don't exist. So react throws this error.

{ this.props.selectedDish && this.renderComments(this.props.selectedDish.comments)}

So this ensures this.props.selectedDish exists and then you are accessing it's comments key.

Upvotes: 2

Related Questions