Home > front end >  Am I not updating my React state correctly?
Am I not updating my React state correctly?

Time:09-30

I hope you can help, I've reduced my code to make it more concise.

Objective: I'm trying to create a callback that removes the first item from the playOrder array every 2000 milliseconds until there are no more items left in the array.

Problem: Each time playOrderHandler runs, the playOrder duplicate assigned is always 4 items even though the useEffect callback is showing that the new playOrder array has been set as expected.

What am I doing wrong?

Any help is much appreciated

    const [chamberSpinning, setChamberSpinning] = useState(false)

    const [playOrder, setPlayOrder] = useState(
        [
            'left',
            'right',
            'left',
            'stop'
        ]
    )

    useEffect(() => {
        if (chamberSpinning) {
            console.log(playOrder, playOrder.length) // logs 3 items
        }
    }, [playOrder])

    function chamberSpinInit () {
        setChamberSpinning(true)
        playOrderHandler()
    }

    function playOrderHandler () {
        if (playOrder.length > 0) {
            const playOrderDup = [...playOrder]
            console.log(playOrderDup) // Logs 4 items every time
            playOrderDup.splice(0,1)
            setPlayOrder(playOrderDup)
            spinIt()
        } else {
            console.log('No more plays remaining')
        }
    }

    function spinIt () {
        setTimeout(() => {
            playOrderHandler()
        }, 2000)
    }

    return (
        <button onClick={() => chamberSpinInit()} >
            SPIN THE CHAMBER!
        </button>
    )

Any help is much appreciated

Thanks all,

Moe

CodePudding user response:

A plain recursive call of spinIt is problematic because the spinIt in scope at the time of that call is the same spinIt that was defined at the time of rendering earlier - it's not the new spinIt that gets created as a result of calling setPlayOrder(playOrderDup) (and the subsequent rerender). Put the recursive call in a useEffect instead, which will close over the proper current value of state.

const { useState, useEffect } = React;
const App = () => {
    const [chamberSpinning, setChamberSpinning] = useState(false);
    const [playOrder, setPlayOrder] = useState(
        [
            'left',
            'right',
            'left',
            'stop'
        ]
    );

    useEffect(() => {
        if (chamberSpinning) {
            console.log(playOrder, playOrder.length);
            spinIt();
        }
    }, [playOrder, chamberSpinning])

    function chamberSpinInit() {
        setChamberSpinning(true)
        playOrderHandler()
    }

    function playOrderHandler() {
        if (playOrder.length > 0) {
            setPlayOrder(playOrder.slice(0, -1));
        } else {
            console.log('No more plays remaining')
            setChamberSpinning(false)
        }
    }

    function spinIt() {
        setTimeout(() => {
            playOrderHandler()
        }, 2000)
    }

    return (
        <button onClick={chamberSpinInit} >
            SPIN THE CHAMBER!
        </button>
    )
};

ReactDOM.createRoot(document.querySelector('.react')).render(<App />);
<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 class='react'></div>

  • Related