Home > other >  setState Updating all instances of Component React
setState Updating all instances of Component React

Time:12-04

I'm trying to create a quizlet-learn kind of app, and I have multiple instances of Card components. I have it set so when I pick an answer on one of the cards, it updates the state and display based on what I picked. There are buttons at the bottom to change the card. The issue is when I pick an answer on a card, the state is updated for every card component and showAnswer is set to true. How can I prevent this from happening and make it only update the same card?

My card component:

export default class Card extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            option_correct: Math.random() > 0.5,
            showAnswer: false,
            optionPicked: 0
        }
    }

    handleClick(optionPicked) {
        console.log(this.state.showAnswer);
        console.log(this.props.prompt   ' 1');
        if (!this.props.hidden) {
            if (!this.state.showAnswer)
                this.setState({
                    optionPicked: optionPicked
                });
            this.setState({
                showAnswer: true
            });
        }
    }

    handleKeyPress(event) {
        console.log(prompt   ' 2');
        if (event.key === 'Enter') {
            this.props.onFinish();
            event.preventDefault();
        }
    }

    render() {

        console.log(this.props.prompt   ' '   this.state.showAnswer);

        return (
            <div className={`card ${this.state.showAnswer ? "show_answer" : ""}`} onKeyDown={this.handleKeyPress}>
                <div className='prompt'>
                    {this.props.prompt}
                </div>
                <small className={'continue'}>
                    Press Enter to Continue...
                </small>
                <div className='options'>
                    <button className={'option '  
                        (this.state.option_correct ? 'correct' : 'wrong')
                          ' '   (this.state.optionPicked === 1 ? 'picked' : 'not_picked')} onClick={() => {
                        this.handleClick(1);
                    }}>
                        {this.state.option_correct ? this.props.answer : this.props.wrongAnswer}
                    </button>
                    <button className={'option '   (this.state.option_correct ? 'wrong' : 'correct')
                          ' '   (this.state.optionPicked === 2 ? 'picked' : 'not_picked')} onClick={() => {
                        this.handleClick(2);
                    }}>
                        {this.state.option_correct ? this.props.wrongAnswer : this.props.answer}
                    </button>
                </div>
            </div>
        );
    }
}

My card container component:

class CardData {
    constructor(prompt, answer, wrongAnswer) {
        this.prompt = prompt;
        this.answer = answer;
        this.wrongAnswer = wrongAnswer;
    }
}

export default function Cards() {
    const [flashcarddata, setFlashcarddata] = useState([]);
    const [current, setCurrent] = useState(0);

    useEffect(() => {
        setFlashcarddata([
            new CardData('word1', 'correct', 'incorrect'),
            new CardData('word2', 'correct 2', 'incorrect 2'),
            new CardData('word3', 'correct 23', 'incorrect 23')
        ])
    }, []);

    const cards = flashcarddata.map((card, index) => {
        return <Card
            prompt={card.prompt}
            answer={card.answer}
            wrongAnswer={card.wrongAnswer}
            hidden={current !== index}
            onFinish={() => {
                console.log('finished '   card.prompt);
                nextCard();
            }}
        />;
    });

    function previousCard() {
        setCurrent(current - 1);
    }
    function nextCard() {
        setCurrent(current   1);
    }

    const loading = <div className="loading">Loading card...</div>;

    return (
        <div>
            {flashcarddata && flashcarddata.length > 0 ? cards[current] : loading}

            <div className="nav">
                {current > 0 ? (
                    <button onClick={previousCard}>Previous card</button>
                ) : (
                    <button className="disabled" disabled>
                        Previous card
                    </button>
                )}
                {current < flashcarddata.length - 1 ? (
                    <button onClick={nextCard}>Next card</button>
                ) : (
                    <button className="disabled" disabled>
                        Next card
                    </button>
                )}
            </div>
        </div>
    );
}

So far I've tried to change Card into a class from a function component, make the handleClick method only work if the card was the one being displayed, and changed from having every card being rendered and having most of them set display: block in css to having just one card rendered at a time.

CodePudding user response:

When you update the state of one Card component, it updates the state of all the Card components because they all have the same initial state.

One way to fix this is to move the state out of the Card component and into the parent Cards component. Then, you can pass the current card's state as a prop to each Card component, and update the state in the Cards component when a card is clicked.

Here's an example of how you could do this:

class CardData {
  constructor(prompt, answer, wrongAnswer) {
    this.prompt = prompt;
    this.answer = answer;
    this.wrongAnswer = wrongAnswer;
  }
}

function Cards() {
  const [flashcarddata, setFlashcarddata] = useState([]);
  const [current, setCurrent] = useState(0);
  const [currentCardState, setCurrentCardState] = useState({
    option_correct: Math.random() > 0.5,
    showAnswer: false,
    optionPicked: 0
  });

  useEffect(() => {
    setFlashcarddata([
      new CardData('word1', 'correct', 'incorrect'),
      new CardData('word2', 'correct 2', 'incorrect 2'),
      new CardData('word3', 'correct 23', 'incorrect 23')
    ]);
  }, []);

  const cards = flashcarddata.map((card, index) => {
    return (
      <Card
        prompt={card.prompt}
        answer={card.answer}
        wrongAnswer={card.wrongAnswer}
        hidden={current !== index}
        showAnswer={currentCardState.showAnswer}
        optionPicked={currentCardState.optionPicked}
        option_correct={currentCardState.option_correct}
        onFinish={() => {
          console.log('finished '   card.prompt);
          nextCard();
        }}
        onClick={() => {
          setCurrentCardState({
            ...currentCardState,
            optionPicked: optionPicked,
            showAnswer: true
          });
        }}
      />
    );
  });

  function previousCard() {
    setCurrent(current - 1);
  }
  function nextCard() {
    setCurrent(current   1);
  }

  const loading = (
    <div className="loading">Loading card...</div>
  );

  return (
    <div>
      {cards}
      <button onClick={previousCard}>Previous</button>
      <button onClick={nextCard}>Next</button>
    </div>
  );
}

In the code above, the state of the Card components is managed by the parent Cards component, and the Card components are rendered based on the current card's state. When a card is clicked, the state is updated in the Cards component, and the new state is passed down to the Card components as props. This ensures that each Card component has its own state and is updated independently of the other Card components.

  • Related