'React cannot set properties of undefined in a functionnal component

Im learning react.js and its functionnal component and I have this issue with a clickable item that triggers a functionnal component : before any click, when the page is loading, i have an error Uncaught TypeError: Cannot read properties of undefined (reading 'comments'). The error is when i call {props.dish.comments} in <RenderComments...

My functional Dishdetail component code is (i removed RenderDish which is not causing an error) :

function RenderComments({ comments }) {
  if (comments != null) {
    const rendercomments = comments.map((comment) => {
      return (
        <p key={comment.id} className="text-start">
          {comment.comment}
          <br />
          <br />
          -- {comment.author},{" "}
          {new Intl.DateTimeFormat("en-US", {
            year: "numeric",
            month: "short",
            day: "2-digit",
          }).format(new Date(Date.parse(comment.date)))}
          <br />
          <br />
        </p>
      );
    });
    return (
      <div className="container">
        <h4 className="text-start">Comments</h4>

        {rendercomments}
      </div>
    );
  } else {
    console.log("renderComments comments is null");
    return <div></div>;
  }
}
const Dishdetail = (props) => {
  return (
    <div className="row">
      <div className="col-12 col-md-5 m-1">
        <RenderDish dish={props.dish} />
      </div>

      <div className="col-12 col-md-5 m-1">
        <RenderComments comments={props.dish.comments} />
      </div>
    </div>
  );
};

export default Dishdetail;

And my caller component is like this :

import React, { Component } from 'react';
import Menu from './MenuComponent';
import {DISHES} from '../shared/dishes'
import Dishdetail from './DishdetailComponent';
import Header from './HeaderComponent'
class Main extends Component {
    constructor(props){
        super(props);
        this.state={dishes:DISHES,
        selectedDish:null,
    };
    }
    onDishSelect(dishId){
        this.setState({selectedDish:dishId});
        console.log(this.state.selectedDish)
    }

    render(){
      return (
        <div className="App">
            <Header/>
            <Menu dishes={this.state.dishes} onClick={(dishId)=> this.onDishSelect(dishId)}/>
            <Dishdetail dish={this.state.dishes.filter((dish)=>dish.id===this.state.selectedDish)[0]} />
        </div>
      );
    }
    }
export default Main;

the dishes.js is like this :

export const DISHES =
    [
        {
        id: 0,
        name:'Uthappizza',
        image: 'assets/images/uthappizza.png',
        category: 'mains',
        label:'Hot',
        price:'4.99',
        description:'A unique combination of Indian Uthappam (pancake) and Italian pizza, topped with Cerignola olives, ripe vine cherry tomatoes, Vidalia onion, Guntur chillies and Buffalo Paneer.',
        comments: [
            {
            id: 0,
            rating: 5,
            comment: "Imagine all the eatables, living in conFusion!",
            author: "John Lemon",
            date: "2012-10-16T17:57:28.556094Z"
            },

EDIT : my Menu code is :

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

    function RenderMenuItem({dish,onClick}){
            return(
                    <Card onClick={()=>onClick(dish.id)}>
                      <CardImg width='100' src={dish.image} alt={dish.name} />
                      <CardImgOverlay>
                        <CardTitle>{dish.name}</CardTitle>

                      </CardImgOverlay>
                    </Card>
            );
    }
    const Menu = (props) => {
        const menu = props.dishes.map((dish) => {
            return (
              <div key={dish.id} className="col-12 col-md-5 m-1">
                <RenderMenuItem dish={dish} onClick={props.onClick}/>
              </div>
            );
        });
        return (
            <div className="container">
                <div className="row">
                      {menu}
                </div>
            </div>

        );

    }




export default Menu;

I tried to do a try typeof comments undefined/catch to manually set {props.dish.comments} to null but i get an error that i cant set an only readable property.

I dont know where to put a if/else on the dish or comments to adapt the code and what is the variable status of dish and comments when i dont click...apparently 'undefined'?

If you could help me to solve this without using a class component that would be great. (Im also a beginner in javascript)



Solution 1:[1]

You can rewrite Main as a functional component using useState.

?? Do not copy dishes to local state ??

import { useState } from "react"

function Main({ dishes = [], initSelection = null }) {
  const [selection, setSelection] = useState(initSelection)
  const selectDish = dishId => event => { setSelection(dishId) }
  return <div className="App">
    <Header />
    <Menu dishes={dishes} onClick={selectDish} />
    { selection == null
      ? null
      : <Dishdetail dish={dishes.find(d => d.id == selection)} />
    }
  </div>
}

Note how Menu uses onClick -

function Menu({ dishes = [], onClick }) {
  return <div className="Menu">
    {dishes.map((dish, key) =>
      <a key={key} onClick={onClick(dish.id)}>{dish.name}</a>
    )}
  </div>
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1