Search code examples
arraysreactjsstateupdating

React Array in State updating late


What I am trying to do is update an array in state by adding a string of the name of the button the user pressed. The user has several buttons so after pressing them all I should be left with an array of strings correlating to the user's button presses.

First I give the user an alert telling them which food was added then I simply attempt to append the name of that food to the array in state. After which I pass that array back to the parent component to update a similar array there.

The problem I am getting is that when I press on one of the food options the array in state does not update, but i do get the correct alert. When I try a second food addition the array in state updates with the first option I selected. From then on every time I try to add a food Item the array in state updates with the food I selected before it.

I believe the problem is that I am updating state in a way that React does not do imminently. I know that state updates are not guaranteed in React.

Sorry for the long post, thank you for your time.

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

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

  componentDidMount() {
    // Build a new array of objects 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 });
  }

  handleClick(e) {
    alert("Added " + e.target.name);
    this.setState(
      {
        addedFoods: [...this.state.addedFoods, e.target.name]
      },
      this.props.updateAddedFoods(this.state.addedFoods)
    );
  }

  render() {
    const displayFood = () => {
      let foodItems = []; // Crate an array
      for (let i = 0; i < this.state.foodData.length; i++) {
        foodItems.push(
          // push item to array through the loop
          <div className="food-card" key={i}>
            <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
              onClick={this.handleClick}
              name={this.state.foodData[i].name}
            >
              Add
            </button>
          </div>
        );
      }
      return foodItems;
    };

    return (
      <div>
        <div className="food-container">
          {!this.state.foodData.length ? <h1>Loading ...</h1> : displayFood()}
        </div>
      </div>
    );
  }
}

export default Food;

Solution

  • Second argument for setState should be a function and not a function call. Also it is better to use setState(oldState => newState) when new state is based on the old one:

      handleClick(e) {
        const name = e.target.name;
        alert("Added " + name);
        this.setState(oldState => ({
            addedFoods: [...oldState.addedFoods, name]
          }),
          () => this.props.updateAddedFoods(this.state.addedFoods)
        );
      }