dbrewster
dbrewster

Reputation: 693

problem with setState and too many re-renders

I am using functional components in react.

I have a Game component that is the display parent. It has a parent called Main which houses all the functions and variables for Game. I am getting the too many re-renders bug when I try to set the state of a particular variable. It is triggered by the startGame() method which calls an API and then calls another function with the data to set 4-6 variables. I can use the setState on 6 of the variables without issue but any time I try to 'setPlayers', it creates a too many re-renders bug.

I am clueless as to why this one variable is special. I temporarily solved the problem by switching Players to a regular variable at which point I can assign its value no problem but I would like to understand what's going on.

Main.jsx

    let [isBet, setIsBet] = useState(false);
    let [betOptions, setBetOptions] = useState();
    let [hasStarted, setHasStarted] = useState(false);
    let [id, setId] = useState(0);
    const [players, setPlayers] = useState([]) ////The variable that causes the issue
        // let players = []; //doesn't cause issue
    const [cards, setCards] = useState([])
    const [hand, setHand] = useState([])
    const [betLog, setBetLog] = useState([])
    const [showModal, setShowModal] = useState(false)
    const [errorMessage, setErrorMessage] = useState("")

    const setVariables= data => {
        setHasStarted(true)
        setId(data.gameId)
        setPlayers(data.users) ///// !!!!! causes too many re-renders bug
        // players = data.users // if I use this line instead of the above, the function works
        // setPlayers([...data.users])
        setHand(data.hand)
        console.log("BET OPTIONS", data.betOptions)
        if (data.betOptions.name === username){
            setBet(data.betOptions)
        } 
    }

    const setBet = betOptions => {
        setBetOptions(betOptions)
        console.log("Your Bet Options are", betOptions)
        console.log(betOptions.betAmount)
        setIsBet(true)
    }

    const startGame = async (state) => {
        let body = { username, 
            displayName : state.displayName,
            numberOfPlayers : state.numberOfPlayers,
            fillWithComputerPlayers: state.fillWithComputerPlayers,
            isCustom: state.isCustom,
            bigBlind: state.bigBlind
         }
        try {
            const data = await Service.startGame(body);
            console.log("response body", data.data)
            setVariables(data.data);
        } catch (err){
            console.error(err)
            setErrorMessage(err.message)
            setShowModal(true)
            setTimeout(function(){
                setShowModal(false)
            }, (2500))
        }
    }

All the variables are being used by the child component, Game. I am not using any UseEffect on 'players' but it is the only one of the relevant variables on which I am using a .map() (note- I am also mapping 'cards' and setting it in exactly the same way but in a different function). That is the only use of the 'players' variable.

Game.jsx

    let id = props.id;
    // let [players] = useState(props.players)
    let players = props.players
    let hand = props.hand;
    let betOptions = props.betOptions;
    let cards = props.cards
    const [money, setMoney] = useState(0)
    const username = props.username;

return ( 
        <div id="background">

         /// Lots of other components not relevant to the question
                {props.hasStarted ? 
                    <div> 
                        {players.map((v, i) => {    /////// !! only use of players
                            if (v.username !== username){
                                return (
                                    <PlayerInfo name={v.username} money={v.money} key={i} class="info" />

                                )
                            } else {
                                setMoney(v.money)
                            }                           
                        })}
                        {cards.length > 0 && cards.map((v, i) => {
                            return (
                                <img className="cards" key={i} src={process.env.PUBLIC_URL + '/pics/PNG/' + v.image} alt={v.image} />
                            )
                        })}
                         <div id="my">
                            <MyInfo name={username} money={money} hand={hand} class="info" /> 
                        </div>      
                    </div>                   
                :
                    <SettingsForm startGame={props.startGame} username={username} />
                }
            
            </div>

            
            <Log betLog={props.betLog} />

        </div>

PlayerInfo.jsx

import "./Info.css"

const PlayerInfo = props => {

    return ( 
        <div className="info">
           <h4>{props.name}</h4> {props.money}$
            <div>          
               <img className="cards" src={process.env.PUBLIC_URL + '/pics/PNG/red_back.png'} alt="card" />
               <img className="cards" src={process.env.PUBLIC_URL + '/pics/PNG/red_back.png'} alt="card" />
            </div>                    
        </div>
     );
}
 
export default PlayerInfo;

Upvotes: 2

Views: 933

Answers (1)

tomleb
tomleb

Reputation: 2525

Why not just turn as many of them as makes sense into 1 or more objects?
Unlike setState for 5 different pieces of state, which would cause 5 separate re-renders,
setting the state as 1 object containing all those 5 pieces would cause only 1 re-render.

Upvotes: 2

Related Questions