I'm busy learning React and one of the tasks is building a game where the cards shuffle on each try. The card array and the shuffle method are in a parent component (I use map to render the cards), but the retry button is in the child component.
I've read almost 20 StackOverflow questions and many docs, but somehow my logic is simply not working.
Can someone please help me to get this to work.
Parent:
import React from 'react'
import FlipCard from './FlipCard'
const cards = [
{
id: 1,
text: 'NOPE',
},
{
id: 2,
text: `!!WIN-
NER!!`,
},
{
id: 3,
text: 'NOPE',
},
]
const shuffle = array => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i 1))
const temp = array[i]
array[i] = array[j]
array[j] = temp
}
return array
}
const shuffleCards = shuffle(cards)
const CardGameUI = () => {
return (
<div className="cards-ui">
{cards.map(card => (
<FlipCard
key={card.id}
text={card.text}
value={card.id}
shuffleCards={shuffleCards}
/>
))}
</div>
)
}
export default CardGameUI
Child:
import React from 'react'
import ReactCardFlip from 'react-card-flip'
import FrontComponent from './FrontComponent'
import BackComponent from './BackComponent'
import RetryModal from './RetryModal'
class FlipCard extends React.Component {
constructor(props) {
super(props)
this.state = {
isFlipped: false,
gamesPlayed: 0,
gamesWon: 0,
gamesLost: 0,
show: false,
gameMsg: '',
}
this.handleFlip = this.handleFlip.bind(this)
this.handleShow = this.handleShow.bind(this)
this.handleClose = this.handleClose.bind(this)
this.handleReset = this.handleReset.bind(this)
this.handleRetry = this.handleRetry.bind(this)
}
handleReset() {
this.setState({
isFlipped: false,
gamesPlayed: 0,
gamesWon: 0,
gamesLost: 0,
show: false,
gameMsg: '',
})
this.props.shuffleCards
}
handleRetry() {
this.setState({
isFlipped: false,
show: false,
gameMsg: '',
})
this.props.shuffleCards
}
handleClose() {
this.setState({
show: false,
})
}
handleShow() {
this.setState({
show: true,
})
}
handleFlip() {
this.setState({
isFlipped: true,
gamesPlayed: 1,
show: false,
})
if (this.props.value !== 2) {
this.setState({
gamesLost: 0,
gameMsg:
'It looks like you lost this round. Want to play another?',
})
} else if (this.props.value === 2) {
this.setState({
gamesWon: 0,
gameMsg: 'CONGRATULATIONS!!!. Want to win again?',
})
}
setTimeout(() => {
this.handleShow()
}, 1000)
}
render() {
const text = this.props.text
const value = this.props.value
const isFlipped = this.state.isFlipped
return (
<>
<ReactCardFlip isFlipped={isFlipped} flipDirection="horizontal">
<FrontComponent onClick={this.handleFlip} />
<BackComponent text={text} value={value} />
</ReactCardFlip>
<RetryModal
handleShow={this.handleShow}
handleClose={this.handleClose}
handleReset={this.handleReset}
handleRetry={this.handleRetry}
show={this.state.show}
text={this.state.gameMsg}
/>
</>
)
}
}
export default FlipCard
I included all the code from these two components as I wasn't sure what will help clarify.
Essentially the shuffleCards method needs to run when handleReset and handleRetry are triggered.
CodePudding user response:
You essentially have 3 problems:
the
shuffleCards
prop you were passing down was not actually a function, but the result of calling the function that shuffles the cards.You seem to recognise the above by doing
this.props.shuffleCards
in the child component - rather thanthis.props.shuffleCards()
which here would cause an error sincethis.props.shuffleCards
is not a function. But that is itself an admission of the problem, because you clearly need to call a function here, in order to make anything happen.The function itself doesn't actually create a new shuffled array, but mutates the old one. Yes, it then returns that, but by returning the old object, even in mutated form, React won't be able to see a change and therefore won't know that the array has been mutated.
So here's how to solve. First, make the card array a state variable in the parent function, and make shuffleCards
an actual function, that lives in the parent component (because it needs to access the current cards
state), and one that, rather than simply mutating the old array of cards, makes a new array and updates the state with it.
Here's the new parent component, and note that the shuffle
and shuffleCards
values you've defined outside it are no longer needed or wanted:
const CardGameUI = () => { const [shuffledCards, setShuffledCards] = React.useState(cards);
const shuffleCards = (currentCards) => {
const array = [...currentCards]; // this makes a (shallow) copy of the array, so it's recognised by React as something new
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i 1))
const temp = array[i]
array[i] = array[j]
array[j] = temp
}
setShuffledCards(array);
};
return (
<div className="cards-ui">
{shuffledCards.map(card => (
<FlipCard
key={card.id}
text={card.text}
value={card.id}
shuffleCards={shuffleCards}
/>
))}
</div>
)
}
And then you need to update the child component so that it actually calls this state-updating function, by changing as I indicated in point 2) above: replace this.props.shuffleCards
with this.props.shuffleCards()
.
The cards should now be correctly shuffled when the appropriate actions are taken in the child component. (And let me know if anything still doesn't work as intended - I haven't actually tested this out.)
CodePudding user response:
let array = [...cards];
const shuffle = () => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i 1))
const temp = array[i]
array[i] = array[j]
array[j] = temp
}
return array
}
Just replace shuffle function by this code ! I hope it works !