Search code examples
reactjsreact-hooksuse-effect

How can I use setInterval to change state with or without useEffect?


I am doing a memory game in which every time the user clicks on a card it executes flipcard passing the card id to it, and changes the flip status of that card, updating the gameState.

After a card is flipped, I want to execute checkMatchingCards to check if the flipped cards are of the same type, if they aren't it must flip the cards back, updating gameState, BUT after a second, not instantly.

What is the best way to assure that both functions are executed in that order, flipcard and then checkMatchingCards with the last one waiting a second?

const flipCard = (id) => {
  if (!canFlipCard(id)) return

  // Find card to flip
  const cardToFlip = gameState.cards.find(card => card.id === id)
  cardToFlip.isFlipped = !cardToFlip.isFlipped

  setGameState({
    ...gameState,
    cards: gameState.cards.map(card => (card.id === cardToFlip.id ? cardToFlip : card)),
    flippedCards: gameState.flippedCards.concat(cardToFlip)
  })
}

const checkMatchingCards = (cards) => {
  if (!cards) return
  const cardsMatch = cards.every(card => card.color === cards[0].color)

  // If cards match and reach number of cards to match
  if (cardsMatch && cards.length === MATCH_GOAL) {
    setGameState({
      ...gameState,
      flippedCards: [],
      matchedCards: gameState.matchedCards.concat(cards),
    })
  }
  // If cards doesn't match, clean flippedCards
  else if (!cardsMatch && cards.length >= 2) {
    setGameState({
      ...gameState,
      cards: gameState.cards.map(card => gameState.flippedCards.includes(card, 0) ? { ...card, isFlipped: false } : card),
      flippedCards: [],
    })
  }
}

I found two ways of doing this, one is using setTimeout so every time the game renders, it checks for matching cards,

setTimeout(() => {
  checkMatchingCards(gameState.flippedCards)
}, 900)

And another way with useEffect and setTImeout, which executes only when flippedCards change:

useEffect(()=> {
  setTimeout(() => {
    checkMatchingCards(gameState.flippedCards)
  }, 900)  
}, gameState.flippedCards)

I leave the entire code here, in case it helps https://github.com/takihama/memory-game


Solution

  • You should use useEffect in this case.

    Don't call setTimeout outside of useEffect, this will be called every time the component re-render (which isn't necessary since checkMatchingCards only depends on gameState.flippedCards).

    And you should passuseEffect dependencies as an array

    useEffect(()=> {
      setTimeout(() => {
        checkMatchingCards(gameState.flippedCards)
      }, 900)  
    }, [gameState.flippedCards])