javascriptreactjsblackjackplaying-cards

How can I get two non-repeated random objects from an array at first render in React?


I'm making a simple app that will simulate a hand of blackjack. I've got the 'hit' functionality (getting one random card), but I'm struggling with getting the two initial random, non repeated cards on first render. I feel like there must be a more elegant solution to what I have in mind (not to mentions it's not working).

I would appreciate any direction on this, since I'm not sure why filtering and updating the original array isn't working.

Here's the code snippet:

import {useState, useEffect} from 'react'
//import useCards from '../hooks/useCards'
import Card from '../components/Card';
import Total from '../components/Total';
import {deckArray} from '../utils/data'

export default function Home(){
    const [dealCards, setDealCards] = useState(false)
    const [usersCards, setUsersCards] = useState([])
    const [deck, setDeck] = useState(deckArray)
    const [isReset, setIsReset] = useState(true)
    const [total, setTotal] = useState(0)
    const [isStarted, setIsStarted] = useState(false)

    useEffect(() => {
        if(dealCards===true){
            const randomCard = deck[Math.floor(Math.random()*deck.length)];
            const newCardsArray = deck.filter(el => el.index !== randomCard.index)
            const chosenCardArray = deck.filter(el => el.index === randomCard.index)

            const chosenCard = chosenCardArray[0]
            setDeck(newCardsArray)
            setUsersCards(prevCards => [...prevCards, chosenCard])
            console.log(newCardsArray.length)
            setDealCards(false)
        }
    }, [usersCards, dealCards, deck])

    useEffect(() => {
        if(isReset){
            setUsersCards([])
            setDeck(deckArray)
            setDealCards(false)
            setTotal(0)
        }
    },[isReset])


    //function to generate two random cards when user presses 'play'
    useEffect(() => {
        if(isStarted){
            //generate two random cards
            const randomCard = deck[Math.floor(Math.random()*deck.length)];
            const newCardsArray = deck.filter(el => el.index !== randomCard.index)
            const chosenCardArray = deck.filter(el => el.index === randomCard.index)
            const chosenCard = chosenCardArray[0]
            setDeck(newCardsArray)
            setUsersCards(prevCards => [...prevCards, chosenCard])

            const randomCard1 = deck[Math.floor(Math.random()*deck.length)];
            const newCardsArray1 = deck.filter(el => el.index !== randomCard1.index)
            const chosenCardArray1 = deck.filter(el => el.index === randomCard1.index)
            const chosenCard1 = chosenCardArray1[1]
            setDeck(newCardsArray1)
            setUsersCards(prevCards => [...prevCards, chosenCard1])
            console.log(newCardsArray1.length)
            setDealCards(false)
        }
    }, [isStarted, deck, dealCards])

    return (
        <>
            <Card usersCards={usersCards} />

            {!isStarted && <button onClick={() => setIsStarted(true)}>PLAY</button>}

            {isStarted && <>
            <Total usersCards={usersCards} total={total} setTotal={setTotal}/>
            <button onClick={() => setDealCards(true)}>HIT</button>
            <button>STAND</button>
            <button onClick={() => setIsReset(true)}>RESET</button>
            </>}
            
        </>
    )
}

Many thanks for any help!


Solution

  • The code here is overusing useEffect to implement logic that should be done with simple event handlers. Only use useEffect when you're dealing with things that can't be determined before the render, like network calls, or depend on a reference to a DOM element outside of the normal rendering flow, like drawing on a <canvas>. These are side effects because they don't directly pertain to building the current render which the rest of the component body is working towards.

    I don't have your utility and component imports, but here's an example that you should be able to adapt to your use case.

    // utility library "import"
    const cards = (() => {
      const shuffle = a => {
        a = a.slice();
    
        for (let i = a.length - 1; i > 0; i--) {
          const j = ~~(Math.random() * (i + 1));
          const x = a[i];
          a[i] = a[j];
          a[j] = x;
        }
    
        return a;
      };
    
      const frz = (...args) => Object.freeze(...args);
      const suits = frz([..."HCSD"]);
      const faces = frz([..."AJQK"]);
      const pips = frz([...Array(9)].map((_, i) => i + 2));
      const ranks = frz([...pips, ...faces]);
      const cards = frz(
        suits.flatMap(s => ranks.map(r => frz({
          rank: r,
          suit: s,
          str: r + s,
          value: isNaN(r) ? (r === "A" ? 11 : 10) : r,
        })))
      );
      const shuffled = () => shuffle(cards);
      return {shuffled};
    })();
    
    const Game = () => {
      const startHandSize = 2;
      const [deck, setDeck] = React.useState(cards.shuffled());
      const [cardsDealt, setCardsDealt] =
        React.useState(startHandSize);
    
      const deal = () => {
        setCardsDealt(startHandSize);
        setDeck(cards.shuffled());
      };
    
      const hit = () => !bust && setCardsDealt(prev => prev + 1);
    
      const cardsInPlay = deck.slice(-cardsDealt);
      const total = cardsInPlay.reduce((a, e) => a + e.value, 0);
      const bust = total > 21;
    
      return (
        <div>
          <button onClick={deal}>Deal</button>
          <button disabled={bust} onClick={hit}>
            Hit
          </button>
          <div>
            {cardsInPlay.map(e => (
              <div key={e.str}>{e.str}</div>
            ))}
          </div>
          <div>Total: {total}</div>
          <div>{bust && "Bust!"}</div>
        </div>
      );
    };
    
    ReactDOM.createRoot(document.querySelector("#app"))
      .render(<Game />);
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <div id="app"></div>