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>