0

I currently have a parent class (App) and a child component (FoodItem). The App component maps a list of FoodItem components.

Each individual FoodItem component has a state called clickCount which increments each time its clicked by the user. However, I need the reset button in my App component to reset the FoodItem state to 0 onClick for all FoodItem components in the map.

Any help with this would be much appreciated.

Update - I have managed to get the reset button to update the FoodItem child component, however it only updates the last item in the mapped list, whereas I require it to update all items in the list.

    class App extends React.Component {

  constructor(props) {
    super(props);
    this.myRef = React.createRef();
    this.state = {
      foods: FoodCards,
      calorieCount: 0,
      vitaminACount: 0,
      vitaminDCount: 0,
    };
    this.increment = this.increment.bind(this);
  }

  increment = (calories = 0, vitaminA = 0, vitaminD = 0) => {
    this.setState({
      calorieCount: Math.round(this.state.calorieCount + calories),
      vitaminACount: Math.round((this.state.vitaminACount + vitaminA) * 100) / 100,
      vitaminDCount: Math.round((this.state.vitaminDCount + vitaminD) * 100) / 100
    });
  };

  resetCounters = () => {
    this.myRef.current.resetClickCount();
    this.setState({
      calorieCount: 0,
      vitaminACount: 0,
      vitaminDCount: 0,
    });
  };

  render() {

    return (
      <div className="App">
  
        <main className="products-grid flex flex-wrap">
  
        {this.state.foods.map((item, i) => {
          return <FoodItem key={item.id} ref={this.myRef} name={item.name} img={item.img} calories={item.calories} vitamin_a={item.vitamin_a} vitamin_d={item.vitamin_d} updateTotals = {this.increment} />
        })}
  
        </main>

        <footer>
          <div className="reset" onClick={() => this.resetCounters()}>Reset</div>
        </footer>
  
      </div>
    );
  }
}

export default App;

class FoodItem extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      clickCount: 0
    };
  }

  handleUpdateTotals = (calories, vitamin_a, vitamin_d) => {
    this.props.updateTotals(calories, vitamin_a, vitamin_d);
    this.clickIncrement();
  }

  clickIncrement = () => {
    this.setState({
      clickCount: this.state.clickCount + 1
    });
  }

  resetClickCount = () => {
    this.setState({
      clickCount: 0
    });
  }

  render() {
    return (
      <div className="product" onClick={() => this.handleUpdateTotals(this.props.calories, this.props.vitamin_a, this.props.vitamin_d)}>
        <p>{this.props.name}</p>
        {this.state.clickCount > 0 ? <p>Selected: {this.state.clickCount}</p> : <p>Not Selected</p>}
        <img src={this.props.img} alt="" />
      </div>
    );
  }
}
BEDev
  • 699
  • 1
  • 5
  • 9
  • Introduce a property in FoodItem such is "reset" and when Reste button is clicked pass the property value as true of "reset". In the FoodItem component set clickCount to 0 if props.reset===true. – Asutosh Jul 26 '20 at 15:14
  • 1
    You should lift the state up and keep the click count in your parent component. https://reactjs.org/docs/lifting-state-up.html – Çağatay Sel Jul 26 '20 at 15:14

1 Answers1

1

You should lift the state up per the react docs. When two children affect the same state (reset button and clickCount), the state should be lifted up to the parent that contains both of these children. So your count should be stored in the parent.

But... to answer your question, you can easily do something like this to trigger a render each time the button is clicked (not recommended though):

const [resetCount, setResetCount] = useState(false);

const resetCountClicked = () => setResetCount(!resetCount);

return 
  <>
    <Child resetCount={resetCount}></Child>
    <button onClick={() => resetCountClicked()}>Reset Count</button>
  </>

And in the child include:

useEffect(() => setCount(0), [resetCount]);

This is using hooks with function components and useEffect to run an event each time the prop resetCount changes.

With classes you can use a ref passed to the child as shown in this post.

const { Component } = React;

class Parent extends Component {
  constructor(props) {
    super(props);
    this.child = React.createRef();
  }

  onClick = () => {
    this.child.current.resetCount();
  };

  render() {
    return (
      <div>
        <Child ref={this.child} />
        <button onClick={this.onClick}>Reset Count</button>
      </div>
    );
  }
}

And in the child...

  resetCount() {
    this.setState({
      clickCount: 0
    });
  }
Diesel
  • 5,099
  • 7
  • 43
  • 81
  • Thanks for this. I used the classes and child ref as I'm still fairly new to React and lifting up state is something I need to get my head around. However, the reset button now only updates the state of the last in the mapped object.. – BEDev Jul 26 '20 at 17:54
  • https://reactjs.org/docs/thinking-in-react.html You should read this. You would need a separate ref for every child... the useEffect method would work well for you - if you are just learning React why not use Function Components? Hooks are newer and the way React is going. – Diesel Jul 26 '20 at 18:45