Search code examples
javascriptreactjssetstate

React increment item count only on first press of button


I have several exercise objects that the user can join by clicking the button, and the Sidebar component will display the number of different exercises that the user joined. Thus, the number of exercises that the user joined should only increment if the user hasn't already joined the exercise.

App.js

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      //array of exercise objects from json file
      exercises: data.exercises, 
      //exercise objects that user joined
      joined: [],
    }
  }
  
  //gets passed as a prop and called when user clicks join exercise button
  incrementCount = (exerciseChosen) => {
    let joined = this.state.joined.slice()
    let exercises = this.state.exercises.slice()

    exercises.forEach((exercise) => {
      if (exercise._id === exerciseChosen._id) {
        if (!joined.includes(exercise)) {
          exercise.count++;
          joined.push({ exercise })
          console.log('after push joined: ' + joined.length + ' exercise count: ' + exercise.count)
        }
      }
    })
    this.setState({ exercises: exercises, joined: joined })
    console.log('after setstate joined: ' + this.state.joined.length)
  }
...

The expected behavior was that if the user hasn't already joined the exercise (the exercise isn't in the joined array), it gets added to the joined array and exercise.count (number of people who joined exercise) gets incremented when the join button is clicked. However, when I click on the join button for the same exercise multiple times, the length of the joined array still goes up. I tried to check if the state gets updated correctly by logging the length of the joined array after calling setstate(), and it seems to be logging out the length of the joined array prior to calling to setstate(), as shown in the attached screenshot. I'm confused about why this is happening and how to fix it. Thanks!

--After clicking on the join button 5 times----- Screenshot of website and console log results


Solution

  • I added a couple comments to the code. One, is that you are directly mutating state, which should be avoided. and Second, you are pushing a new, nested object to joined so that is throwing it off.

    class App extends React.Component {
      constructor() {
        super();
        this.state = {
          //array of exercise objects from json file
          exercises: data.exercises, 
          //exercise objects that user joined
          joined: [],
        }
      }
      
      //gets passed as a prop and called when user clicks join exercise button
      incrementCount = (exerciseChosen) => {
        let joined = this.state.joined.slice()
        let exercises = this.state.exercises.slice()
    
        exercises.forEach((exercise) => {
          if (exercise._id === exerciseChosen._id) {
            if (!joined.includes(exercise)) {
            /* You are directly mutating state here with `exercise.count++`,
               although exercises is a new array, each exercise object is 
               still a reference to the same objects in state */
              exercise.count++;
              /* here I think you want `joined.push(exercise)` */
              joined.push({ exercise })
              console.log('after push joined: ' + joined.length + ' exercise count: ' + exercise.count)
            }
          }
        })
        this.setState({ exercises: exercises, joined: joined })
        console.log('after setstate joined: ' + this.state.joined.length)
      }
    ...
    

    Here's what I would suggest instead:

    class App extends React.Component {
      constructor() {
        super();
        this.state = {
          //array of exercise objects from json file
          exercises: data.exercises, 
          //exercise objects that user joined
          joined: [],
        }
      }
      
      //gets passed as a prop and called when user clicks join exercise button
      incrementCount = (exerciseChosen) => {
        let joined = this.state.joined.slice()
    
        let exercises = this.state.exercises.map((exercise) => {
          if (exercise._id === exerciseChosen._id) {
            if (!joined.find(e => e._id === exercise._id)) {
              exercise = {...exercise, count: exercise.count + 1}
              joined.push(exercise)
              console.log('after push joined: ' + joined.length + ' exercise count: ' + exercise.count)
            }
          }
          return exercise;
        })
        this.setState({ exercises: exercises, joined: joined })
        console.log('after setstate joined: ' + this.state.joined.length)
      }
    ...