Home > Back-end >  How can I call a function from the parent component with an event in the child?
How can I call a function from the parent component with an event in the child?

Time:02-08

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:

  1. the shuffleCards prop you were passing down was not actually a function, but the result of calling the function that shuffles the cards.

  2. You seem to recognise the above by doing this.props.shuffleCards in the child component - rather than this.props.shuffleCards() which here would cause an error since this.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.

  3. 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 !

  •  Tags:  
  • Related