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.