admin管理员组文章数量:1386656
So I'm building a drum-pad type of app, and almost everything is working, except this.
Edit: Put the whole thing on codesandbox, if anyone wants to have a look: codesandbox.io/s/sleepy-darwin-jc9b5?file=/src/App.js
const [index, setIndex] = useState(0);
const [text, setText] = useState("");
const [theSound] = useSound(drumBank[index].url)
function playThis(num) {
setIndex(num)
}
useEffect(()=>{
if (index !== null) {
setText(drumBank[index].id);
theSound(index);
console.log(index)
}
}, [index])
When I press a button, the index changes to the value associated with the button and then the useEffect hook plays the sound from an array at that index. However, when I press the same button more than once, it only plays once, because useState doesn't re-render the app when the index is changed to the same value.
I want to be able to press the same button multiple times and get the app to re-render, and therefore useEffect to run every time I press the button. Can anyone help me how to do this?
So I'm building a drum-pad type of app, and almost everything is working, except this.
Edit: Put the whole thing on codesandbox, if anyone wants to have a look: codesandbox.io/s/sleepy-darwin-jc9b5?file=/src/App.js
const [index, setIndex] = useState(0);
const [text, setText] = useState("");
const [theSound] = useSound(drumBank[index].url)
function playThis(num) {
setIndex(num)
}
useEffect(()=>{
if (index !== null) {
setText(drumBank[index].id);
theSound(index);
console.log(index)
}
}, [index])
When I press a button, the index changes to the value associated with the button and then the useEffect hook plays the sound from an array at that index. However, when I press the same button more than once, it only plays once, because useState doesn't re-render the app when the index is changed to the same value.
I want to be able to press the same button multiple times and get the app to re-render, and therefore useEffect to run every time I press the button. Can anyone help me how to do this?
Share Improve this question edited Mar 10, 2021 at 10:12 Lursmani asked Mar 10, 2021 at 7:48 LursmaniLursmani 1691 gold badge1 silver badge12 bronze badges 6- I found this custom hooks for key press may be that can help you useKeyPress – antoineso Commented Mar 10, 2021 at 8:05
-
Why use an
useEffect
hook at all for this and not just associate a keyPress event listener callback with a specific index value to pass totheSound
? Perhaps I'm missing some context in this minimal example? TheuseEffect
is intended to run an effect when a dependency changes, not when an asynchronous event happens. It doesn't seem to be the correct tool for the job. – Drew Reese Commented Mar 10, 2021 at 8:09 -
@DrewReese is right, why use
useEffect
when it's not beneficial to you whatsoever. Just wrap all in one callbackplayThis
is enough. – vuongvu Commented Mar 10, 2021 at 8:20 - @DrewReese I tried that at first, putting everything within playThis(), but useSound is a custom hook that can not be inside a normal function. That's why I have it set to play the sound at index and not num. And if I put it in its current form within playThis, it lags behind and plays the sound at the previous index because state is asynchronous. – Lursmani Commented Mar 10, 2021 at 8:47
-
You don't need to put
useSound
in any callback, and for other than obvious hook reasons, buttheSound
can. I think a React ref may help you here. Do you have the ability to create a running codesandbox with this code mostly working? I have an idea but I am just not certain where thetext
state ordrumBank
e into play. – Drew Reese Commented Mar 10, 2021 at 9:01
2 Answers
Reset to default 2Here's what I could e up with from your sandbox.
According to the docs each
useSound
is just a single sound, so when trying to update an index into a soundbank to use via React state the sound played will always be at least one render cycle delayed. I suggest creating a new custom hook to encapsulate your 9 drum sounds.useDrumBank
consumes the drumbank array and instantiates the 9 drum sounds into an array.const useDrumBank = (drumbank) => { const [drum0] = useSound(drumbank[0].url); const [drum1] = useSound(drumbank[1].url); const [drum2] = useSound(drumbank[2].url); const [drum3] = useSound(drumbank[3].url); const [drum4] = useSound(drumbank[4].url); const [drum5] = useSound(drumbank[5].url); const [drum6] = useSound(drumbank[6].url); const [drum7] = useSound(drumbank[7].url); const [drum8] = useSound(drumbank[8].url); return [drum0, drum1, drum2, drum3, drum4, drum5, drum6, drum7, drum8]; };
Update the ponent logic to pass the
drumBank
array to the new custom hook.const sounds = useDrumBank(drumBank);
Here's the full code:
function App() {
useEffect(() => {
document.addEventListener("keypress", key);
return () => document.removeEventListener("keypress", key);
}, []);
const [text, setText] = useState("");
const sounds = useDrumBank(drumBank);
function playThis(index) {
drumBank[index]?.id && setText(drumBank[index].id);
sounds[index]();
}
function key(e) {
const index = drumBank.findIndex((drum) => drum.keyTrigger === e.key);
index !== -1 && playThis(index);
}
return (
<div id="drum-machine" className="drumpad-container">
<div id="display" className="drumpad-display">
<p>{text}</p>
</div>
<button className="drum-pad" id="drum-pad-1" onClick={() => playThis(0)}>
Q
</button>
<button className="drum-pad" id="drum-pad-2" onClick={() => playThis(1)}>
W
</button>
<button className="drum-pad" id="drum-pad-3" onClick={() => playThis(2)}>
E
</button>
<button className="drum-pad" id="drum-pad-4" onClick={() => playThis(3)}>
A
</button>
<button className="drum-pad" id="drum-pad-5" onClick={() => playThis(4)}>
S
</button>
<button className="drum-pad" id="drum-pad-6" onClick={() => playThis(5)}>
D
</button>
<button className="drum-pad" id="drum-pad-7" onClick={() => playThis(6)}>
Z
</button>
<button className="drum-pad" id="drum-pad-8" onClick={() => playThis(7)}>
X
</button>
<button className="drum-pad" id="drum-pad-9" onClick={() => playThis(8)}>
C
</button>
</div>
);
}
Demo
Usage Notes
No sounds immediately after load
For the user's sake, browsers don't allow websites to produce sound until the user has interacted with them (eg. by clicking on something). No sound will be produced until the user clicks, taps, or triggers something.
Getting the keypresses to consistently work seems to be an issue and I don't immediately have a solution in mind, but at least at this point the button clicks work and the sounds are played synchronously.
Have you considered bining those discrete state variables (value types) into a single reference type state object?
Instead of having an effect that sets the text when the index changes you just set the entire state at the same time.
As long as you ensure this is a new object/reference then the effect will fire. The effect is then only responsible for playing the sound based on the current state.
Here's some sample code
const [state, setState] = useState({ index: 0, text: '', });
const playThis = (num) => {
setState({ index: num, text: drumBank[num].id, });
};
useEffect(() => {
theSound(state.index);
}, [state]);
版权声明:本文标题:javascript - React hooks - how to force useEffect to run when state changes to the same value? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744534004a2611205.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论