Kevin T.
Kevin T.

Reputation: 758

Adding values to a state array in React

I'm trying to accomplish two things. One, onClick, I am adding values to a state property, cardPairs. I made a copy of the state object and used concat to add a value. It takes me three clicks to create a match. The first time I click, I get the empty array, then on two subsequent correct clicks, I'll have a match. Using push doesn't work on my copy, I get a "could not consume" error and when trying push, I get "object is not extensible". How can I match on two clicks? The second issue is removing the matched items from the UI.

class CardCollectionTwo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      hidden: false,
      cardPairs: []
    };
    this.compareCards = this.compareCards.bind(this);
  }

  compareCards(e) {
    const pairs = Object.freeze(this.state.cardPairs)
    console.log(pairs)
    this.setState({ cardPairs: pairs.concat(e.target.value) });
    console.log(this.state);
    if(pairs[0] === pairs[1] && pairs.length!== 0){
      console.log('MATCH')
    }else if(pairs.length === 2){
        this.setState(prevState => ({ cardPairs: [] }))
   }
 }
 render() {
    let cards = this.props.cardsEasy["cards"].map((item, index) => {
      return (
    <button
      style={btn}
      value={item}
      id={item}
      onClick={this.compareCards}
      key={item + index}
    >
      {item}
    </button>
  );
});
return <CardTwo cards={cards} />;
 }
   }

export default CardCollectionTwo;

Upvotes: 1

Views: 4284

Answers (1)

Chau
Chau

Reputation: 591

Push and Object.freeze()

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze

Object.freeze() prevents elements from being added to the array (in your case, via push). Object.freeze() prevents additions, removals, and changes to whatever it is applied to (array pushes/modifications, objects, etc). To properly copy, you can use the Array.slice() (this.state.cardPairs.slice()), which returns a shallow copy of the source.

concat does not modify the array, while push does. concat() returns a new array that is the combination of 2 arrays, while push() adds an element to the current array. This is why push() does not work on a frozen object, while concat does.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice

Answering the matching question:

if (pairs[0] === pairs[1] && pairs.length!== 0){
  console.log('MATCH')
} else if(pairs.length === 2){
    this.setState(prevState => ({ cardPairs: [] }))
}

There is an error in your logic. Basically, you concatenate the new pair value and commit it to your state (via this.setState({ cardPairs: pairs.concat(e.target.value })). However, this if conditional is relying on that e.target.value, and so the pairs variable is outdated.

Also, it seems code-wise you are strictly only comparing the first two elements of the array, which leads to the infinite mismatch problem.

Solution

We can take a different approach: Firstly, whenever a new, unique value is clicked, add it to the array. However, if we come upon a value that is already in the array, then that means we have a match. We can then filter out the element (there is probably a remove function that'd work well too). That means that our pairs will always only contain unique values, and will drop unique values whenever there are matches.

const pairs = this.state.cardPairs.slice();
if (pairs.includes(e.target.value)) {
  console.log("Match");
  this.setState({ cardPairs: pairs.filter(el => el != e.target.value )})
} else {
  // Otherwise, add more elements to the list.
  this.setState({ cardPairs: pairs.concat(e.target.value) });
}

User clicks 1: [1]

User clicks 1 again: []

User clicks 0, 1: [0,1]

User clicks 1: [0]

Sandbox: https://codesandbox.io/s/4jmjp3l94x

Upvotes: 2

Related Questions