2

I have a component that is supposed to display a "cards" containing information saved in an array in state. I get this information from a JSON file and save it into my array in a "componentDidMount". I then made a function called "displayFood" before the return of my render function, which I then use in my render functions return. The problem is that when I run the code no cards are being displayed.

import React from 'react'
import "./style.scss";
import FoodData from "./foodData.json";

class Food extends React.Component {
    constructor(props){
        super(props);
        this.state={
            foodData: []
        }
    }

    componentDidMount(){
        FoodData.map((foodItem,index) => {
            var foodTemp = {
                id: index,
                name: foodItem.name,
                pic: foodItem.pic,
                serving: foodItem.serving,
                calories: foodItem.calories,
                sugar: foodItem.sugar,
                protien: foodItem.protien
            }
            this.setState((prevState) => {
                prevState.foodData.push(foodTemp)
            })
        })
    }

  render() {

    const displayFood = () => {
        var i;
        for(i = 0; i < this.state.foodData.length; i++){
            return(
                <div className="food-card">
                    <img src={require(`${this.state.foodData[i].pic}`)}/>
                    <ul>
                        <li>{this.state.foodData[i].name}</li>
                        <li>Serving: {this.state.foodData[i].serving}</li>
                        <li>Calories: {this.state.foodData[i].calories}</li>
                        <li>Sugar: {this.state.foodData[i].sugar}</li>
                        <li>Protien: {this.state.foodData[i].protien}</li>
                    </ul>
                    <button name={this.state.foodData[i].name}>Add</button>
                </div>
            )
        }
    }

      return(
        <div>
            <div className="food-container">
            {
                displayFood()       
            }
            </div>
        </div>
      )
  }
}

export default Food;
LAN_scape
  • 77
  • 2
  • 13

4 Answers4

3

First, I am not sure your setState incomponentDidMount work. If you don't want to return a new array, you shouldn't use map, use forEach instead. And you're mutating directly the state, which not recommended. The function should like this:

componentDidMount(){
  // Build a new array of object from FoodData
  const newFoodData = 
        FoodData.map(({name, pic, serving, calories, sugar, protien }, index) => 
                     ({ id: index, name, pic, serving, calories, sugar, protien }))
  // Assign the new object to state
  this.setState({ foodData: newFoodData })
}

Second, return in for loop will return the first item only. You should create an array then push item through the loop instead of return

const displayFood = () => {
    let cards = [] // Crate an array
    for(let i = 0; i < this.state.foodData.length; i++){
        cards.push( // push item to array through the loop
            <div className="food-card">
                <img src={require(`${this.state.foodData[i].pic}`)}/>
                <ul>
                    <li>{this.state.foodData[i].name}</li>
                    <li>Serving: {this.state.foodData[i].serving}</li>
                    <li>Calories: {this.state.foodData[i].calories}</li>
                    <li>Sugar: {this.state.foodData[i].sugar}</li>
                    <li>Protien: {this.state.foodData[i].protien}</li>
                </ul>
                <button name={this.state.foodData[i].name}>Add</button>
            </div>
        )
    }
  return cards  // Return the array
}

And I recommend you to move the displayFood outside the render() function. Because when other things cause re-render but not the this.state.foodData change, the component have to re-build the displayFood(). That's wasted.

bird
  • 1,872
  • 1
  • 15
  • 32
  • Dam bird you nailed it on the head. I implemented your suggestions along with changing the "DidMount" to "WillMount" and it works great. I wish I had more stackoverflow reputation to give you a visible upvote. Thanks again – LAN_scape Mar 21 '19 at 02:15
  • You're welcome! Actually, you should put it in `componentDidMount`, just add handle the empty fooData in the render() function. i.e. `if(!this.state.dataFood.length) return

    Loading...

    `. Because, in real App, the data will not come from JSON file but from API, `componentWillMount` cannot help you a that time. And from version 17, 'componentWillMount' will be deprecated https://reactjs.org/docs/react-component.html#unsafe_componentwillmount
    – bird Mar 21 '19 at 02:26
  • Great to know and will do. I heard about the deprecation of "compnentWillMount" but had not looked into it yet. You seem very knowledgeable on React. – LAN_scape Mar 21 '19 at 03:03
0

According to the lifecycle, the render method is actually already called before componentDidMount. Hence, by the time it is rendered, the data is still empty. Why not move data initialization in componentWillMount?

justelouise
  • 714
  • 1
  • 5
  • 20
  • Thanks justelouise I went ahead and changed the "DidMount" to "WillMount" and now it displays the first object in the array correctly. Hopefully I can troubleshoot why the for loop is only giving me one result. (i tried to up vote you but because I have no reputation on this site it wont show) – LAN_scape Mar 21 '19 at 01:57
0

try to change the previous state... you used directly the data on state, it not changes the state root:

this.setState((prevState) => ({
    ...prevState,
    foodData: [ ...foodData, foodTemp ],
}))
Joao Polo
  • 2,153
  • 1
  • 17
  • 26
  • Thanks I made the changes and get the same result of the program only displaying the first item in the Array of state – LAN_scape Mar 21 '19 at 02:10
0
class Hello extends React.Component {

    constructor(props){
    super(props);
    this.state={
      foodData: []
    }
  }

  componentDidMount(){
    const  { foodData } = this.state;

    foodData.push({
        id: 0,
      name: 'beans',
      pic: 'picurl',
      serving: '2',
      calories: '100',
      sugar: '30',
      protien: '10'
    });

    this.setState({ foodData });

  }

  render() {
    return <div>
      <div className="food-container">
        {
          this.state.foodData.map((item,key) => {
            return <div className="food-card">
              {`${item.pic}`}
              <ul>
                <li>{item.name}</li>
                <li>Serving: {item.serving}</li>
                <li>Calories: {item.calories}</li>
                <li>Sugar: {item.sugar}</li>
                <li>Protien: {item.protien}</li>
              </ul>
              <button name={item.name}>Add</button>
            </div>
          })     
        }
      </div>

    </div>;
  }
}

ReactDOM.render(
  <Hello />,
  document.getElementById('container')
);


make sure that your foodData is loaded correctly on 'componentDidMount()'

https://jsfiddle.net/leolima/L7c418kp/13/

Leonardo Lima
  • 474
  • 5
  • 12
  • Thanks for the responce Leo. It does look like that would also work but I have already got a solid answer from Bird. Thanks for helping out though. – LAN_scape Mar 21 '19 at 02:25