0

Working on my project I faced this kind of problem and tried to solve it on completely clean vite react project but it is still here.

import { useEffect, useState } from 'react'
import reactLogo from './assets/react.svg'
import './App.css'

function App() {
  const [count, setCount] = useState(0)
  const [deck, setDeck] = useState<number[]>([1,2,3,4,5])

  function drawCard() {
    let tmpDeck = deck
    let randomCard = tmpDeck[Math.floor(Math.random() * tmpDeck.length)]
    tmpDeck = tmpDeck.filter(card => card !== randomCard)
    return tmpDeck
  }

  useEffect(() => {
    if(count >= 1) {
      for(let i = 0; i < 2; i++){
        let tmpDeck = drawCard()
        setDeck(tmpDeck)
        console.log(deck);
        
        
      }
    }
  }, [count])

  return (
    <div>
      <div className="card">
        <button onClick={() => {setCount(count+1)}}>
          test
        </button>
      </div>
      <p>
        {deck}
      </p>
    </div>
  )
}

export default App

My goal is to delete 2 elements from a deck by clicking the button. By increasing count I expecting useEffect to call once and with for loop deleting 2 random elements. I tried doing things like console.log in the loop and it prints twice as expected. Than I tried console.log in the function and it prints twice as well. But when it comes to setDeck() from function or setDeck() from useEffect's loop it happens only once.

blasw
  • 3
  • 2
  • `setDeck` is stored in the state, it does not behave like a normal variable, if you call it multiple time (very fast such as in a loop), it get updated (you get reaction) only once, React does that to reduce the number of render – phoenixstudio Dec 01 '22 at 22:19
  • This is state-management, not a side effect. It should not be done with useEffect. As the other commenter said you need to be managing your state in callbacks. – Chad S. Dec 01 '22 at 22:38

1 Answers1

0

Set state functions are async so the changes are not reflected immediately.

You can fix your issue by deleting 2 items at once like this:

function drawCards() {
  let tmpDeck = [...deck]
  let firstRandomCard = tmpDeck[Math.floor(Math.random() * tmpDeck.length)]
  let secondRandomCard = tmpDeck[Math.floor(Math.random() * tmpDeck.length)]
  tmpDeck = tmpDeck.filter(card => card !== firstRandomCard && card !== secondRandomCard);
  return tmpDeck;
}

useEffect(() => {
  if(count >= 1) {
    let tmpDeck = drawCards()
    setDeck(tmpDeck)
    console.log(tmpDeck);
  }
}, [count])

Update:

If you want to use loop, this solution will help you:

function drawCard(tempDeck) {
  const tmpDeck = tempDeck ? [...tempDeck] : [...deck];
  const randomCard = tmpDeck[Math.floor(Math.random() * tmpDeck.length)];
  return tmpDeck.filter(card => card !== randomCard);
}

useEffect(() => {
  if(count >= 1) {
    let tempDeck = [...deck];
    for(let i = 0; i < 2; i++){
      tempDeck = drawCard(tempDeck);
    }
    setDeck(tempDeck);
  }
}, [count]);
Amirhossein
  • 1,819
  • 1
  • 6
  • 10
  • Well I need to do something with the fact that it is async. However i wanted to do it with loop. In my initial project i had a deck [1,2,3,4,5,6,7,8,9,10,11] and player's hand that is empty []. Than I want to get random card from deck and add it to player's hand until it hits >=21. This is why i'm trying to do it in a loop atleast twice. – blasw Dec 01 '22 at 22:27
  • This is state-management, not a side effect. It should not be done with useEffect. – Chad S. Dec 01 '22 at 22:37
  • @blasw I updated my answer. What about this one ? Is this what you need ? – Amirhossein Dec 02 '22 at 08:02