On the click of a start/stop button, i trigger the handlePlay function. The handlePlay function in turn, triggers the playBeat function. Ideally, the play beat function should console.log 1222122212221222..., until i press the button to stop. At the moment it gives me 111111111111111.The state of the currentBeat does not increment which is a problem. I am using the setCurrentBeat to increment the currentbeat by 1 with each render, but this does not seem to increment.
Check out the full code.
function App() {
const [bpm, setBpm] = useState(100);
const [isplaying, setIsPlaying] = useState(false);
const [numberOfBeats, SetNumberOfBeats] = useState(4);
const [currentBeat, setCurrentBeat] = useState(0);
const increment = useRef(null);
// const kick = new Audio(audio2);
// const snare = new Audio(audio3);
const hey = 1;
const bey = 2;
const handlePlay = () => {
if (isplaying) {
clearInterval(increment.current);
setIsPlaying(false);
setCurrentBeat(0);
} else {
increment.current = setInterval(() => {
// console.log("hello");
playBeat();
}, (60 / bpm) * 1000);
setIsPlaying(true);
setCurrentBeat(0);
}
};
const playBeat = () => {
if(currentBeat % numberOfBeats === 0){
console.log(hey)
}else{
console.log(bey)
}
setCurrentBeat(currentBeat 1)
};
const handleInc = () => {
bpm < 300 && setBpm(Number(bpm) 1);
};
const handleDec = () => {
bpm > 0 && setBpm(bpm - 1);
};
const handleSlider = (e) => {
if (isplaying) {
clearInterval(increment.current);
increment.current = setInterval(() => {
playBeat();
}, ((60 / bpm) * 1000));
setCurrentBeat(0)
setBpm(e.target.value);
} else {
setBpm(e.target.value);
}
};
return (
<div className="App">
<h1>Metronome</h1>
<p>{bpm} BPM</p>
<button onClick={handleDec}>
<AiOutlineMinusCircle />
</button>
<input
type="range"
value={bpm}
name=""
id=""
min={0}
max={300}
step="1"
onChange={handleSlider}
/>
<button onClick={handleInc}>
<AiOutlinePlusCircle />
</button>
<br />
<button
onClick={handlePlay}
alt={isplaying ? "stop button" : "start button"}
>
{isplaying ? "stop" : "start"}
</button>
<button>tap tempo</button>
</div>
);
}
export default App;
CodePudding user response:
If you want to incremement a state based on its previous value, you have to do it like so:
setCurrentBeat(previous => previous 1)
CodePudding user response:
when you use setState function in eventHandler or in some function works in asynchronous, you can't guarantee setState reads fresh state. it probably reads stale state.
there are three workaround this.
use useReducer which reducer always references fresh state.
use callback function as a parameter of setState. in your case, setState(prev => prev 1);
save your state values in useRef and ensure your event handler / asynchronous callback function read that ref values. those ref values should be updated each times when component re-render.
as using above workaround is not a ideal way,
there is a new RFC - useEvent has been proposed.
Also, I recommend you handle timeout function in useEffect, not directly in handler.
useEffect(() => {
const nodeTime = setInterval(() => {
...
})
return () => {
clearInterval(nodeTime)
}
},[someState])