skip to Main Content

I am trying to write a simple dice game in React, which randomizes the order of players before play begins.

I am trying to use a function for shuffling arrays which has worked in a vanilla JavaScript version of the game I wrote:

export default function shuffle(array) {
  for (let i = array.length - 1; i > 0; i--) {
    let j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
    return array;
  }
}

(I took this code from here
originally and added the ‘return array’ out of desperation though it wasn’t needed in my plain JavaScript version).

I have tried two ways of achieving what I want to do in React, first with a function called on click:

function shufflePlayers () {
    const shuffledPlayers=shuffle([...players]);
    setPlayers(shuffledPlayers);
  }

Which is then passed as a prop to the "Play!" button:

{play === false && players.length > 0 && <PlayButton togglePlay={togglePlay} shufflePlayers={shufflePlayers}/>}`

And then used like this:

import React from 'react'

export default function PlayButton(props) {
   function handleClick () {
     props.shufflePlayers();
     props.togglePlay();
   }
    return (
    <>
    <button onClick={handleClick}>Play!</button>
    </>
  )
}

Before being rendered like this:

import React from 'react'

export default function PlayButton(props) {
    function handleClick () {
      // props.shufflePlayers();
      props.togglePlay();
    }
    return (
    <>
    <button onClick={handleClick}>Play!</button>
    </>
  )
}

This does not crash, but it does not change the order of the players on screen.

The other way I have tried is with useEffect:

useEffect(()=> {
    const shuffledPlayers=shuffle([...players]);
    setPlayers(shuffledPlayers);
  },[play])

Note that play is a Boolean which is false when setting up the game (adding/removing players) and false when playing the game.

And following error messages:

useEffect(()=> {
    const shuffledPlayers=shuffle([...players]);
    setPlayers(shuffledPlayers);
  },[players])

Note that players is an array of player objects.

But these either throw errors or appear to get stuck in an infinite loop as nothing renders.

I tried all of the above, and also consulted the following Stack Exchange threads:

React Hooks: Shuffle array immediately on load, and onClick

unable to shuffle array correctly in react

From these I got the idea of spreading the array before sending it to my function.

So my question is, how can I reliably re-order the players when the user clicks the "Play" button, and changes the play state variable from false to true? (I don’t mind at this stage if it means the order of players will be randomized again when the player returns to the start after a game is reset).

2

Answers


  1. Chosen as BEST ANSWER

    As pointed out by @derpirscher, the issue was with how I was trying to do things in React, but with my shuffle function, the return being inside the loop. Correcting the shuffle function to:

    export default function shuffle(array) {
      for (let i = array.length - 1; i > 0; i--) {
        let j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
      }
      return array; 
    }
    

    meant that I could call the shufflePlayers function as a prop of the Play! button.


  2. This code is bad form. It will infinitely loop.

    useEffect(() => {
      const shuffledPlayers = shuffle([...players]);
      setPlayers(shuffledPlayers);
    }, [players]);
    

    You should use the function version instead:

    const [players, setPlayers] = useState([]);
    
    useEffect(() => {
      setPlayers((currentPlayers) => shuffle([...currentPlayers]));
    }, []);
    

    Also, you want to be as immutable as possible when using React. Spreading the array into the shuffle function will pass a copy into it, but you have to make sure you return.

    /* Does not modify the incoming array */
    const shuffle = (array) => {
      const sortedArr = structuredClone(array);
      for (let i = sortedArr.length - 1; i > 0; i--) {
        let j = Math.floor(Math.random() * (i + 1));
        [sortedArr[i], sortedArr[j]] = [sortedArr[j], sortedArr[i]];
      }
      return sortedArr;
    }
    
    const players = ['one', 'two', 'three', 'four'];
    const shuffledPlayers = shuffle(players);
    
    console.log(...shuffledPlayers);  // <random>
    console.log(...players);          // [one, two, three, four]
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search