简体   繁体   English

如何在 React 中获取 setInterval 以更改 state 变量?

[英]How to get setInterval in React to change a state variable?

I want the following code to start counting down from a number after a user logs in.我希望以下代码在用户登录后从一个数字开始倒计时。

The following code shows simple shows 0 in the console.log every second, but doesn't seem to set the state variable secondsLeft to 8, nor does it count this variable down.以下代码显示了简单的每秒在 console.log 中显示0 ,但似乎没有将 state 变量secondsLeft设置为 8,也没有将此变量计数。

const [secondsLeft, setSecondsLeft] = useState(0);

... ...

const handleButton = async () => {
    const response = await fetch('http://localhost:5001/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username, password })
    });
    setUsername('');
    setPassword('');
    if (response.ok) {
        const data = await response.json();
        setCurrentUser(prev => ({ ...prev, ...data.user }));
        setMessage(`User: ${currentUser.firstName}`);
        setSecondsLeft(8);
        setInterval(() => {
            setSecondsLeft(prev => secondsLeft -1);
            console.log(secondsLeft);
        }, 1000);
    } else {
        setMessage('bad login');
    }
}

How can I get setInterval to decrease the value of secondsLeft each second?如何让 setInterval 每秒减少secondsLeft的值?

This is a very common pitfall in React.这是 React 中一个非常常见的陷阱。 You, understandably, seem to assume that setSecondsLeft takes effect immediately, but it doesn't.可以理解,您似乎假设setSecondsLeft立即生效,但事实并非如此。 Hence, secondsLeft will not be 8 to start with.因此, secondsLeft开始就不是 8。 There is another problem that secondsLeft is not updated in your interval.还有一个问题是secondsLeft在您的时间间隔内没有更新。 This should work instead:这应该起作用:

    setSecondsLeft(8);
    setInterval(() => {
        setSecondsLeft(prev => prev - 1);
        console.log(prev);
    }, 1000);

I tried to do the things in the traditional way,我试着用传统的方式做事,

useEffect(() => {
  setInterval(() => {
     console.log("update", st);
     setSt((prev) => prev + 1);
  }, 500);
}, []);

But this doesn't work because during the running of useState on the first render, the setInterval keeps a copy of prev for itself which it tries to update again and again, for eg: setSt(prev => prev+1) but since prev is always = 1 due to the closure that it has for itself, the value don't really update.但这不起作用,因为在第一次渲染时运行useState期间,setInterval 为自己保留了copy of prev它会尝试一次又一次地更新,例如: setSt(prev => prev+1)但因为 prev总是 = 1,因为它自己的闭包,值并没有真正更新。


Then I tried to use useCallback hook which rerenders the function on dependency change and thus provided prev as dependency but sadly that also hand't worked most probably due to same reason as above but by different means.然后我尝试使用useCallback钩子,它在依赖项更改时function并因此提供prev作为依赖项,但遗憾的是,由于与上述相同的原因但通过不同的方式,这很可能也不起作用。

You can see the way I tried in the commented out code of codesandbox .您可以在代码和codesandbox的注释掉代码中看到我尝试的方式。


At last, I tried my hands by creating a setIntervalHack which can work similar to setInterval, but is not really a setInterval.最后,我尝试了创建一个setIntervalHack ,它的工作原理类似于 setInterval,但不是真正的 setInterval。

Here are the steps I followed.这是我遵循的步骤。

  • You can think of setInterval as a recursive function that runs again and again after an interval , but the drawback is that we can't pass an argument to it due to which update becomes stale.您可以将setInterval as a recursive function ,它会在一个间隔后一次又一次地运行,但缺点是我们无法将参数传递给它,因为更新变得陈旧。
  • So, I passed an argument to the setIntervalHack which depicts the new state to be updated with.因此,我向setIntervalHack传递了一个参数,该参数描述了要更新的新 state。
  • To wait for some time, I used promises to await for the recursion again to run and in the argument of recalling recursive function setIntervalHack , I passed (currentState+1)为了等待一段时间,我使用承诺await递归再次运行,并在调用递归 function setIntervalHack的参数中,我通过了(currentState+1)

and this is the way I tried to achieve the similar functionality of setInterval这就是我试图实现setInterval类似功能的方式

Here is the link to the code https://codesandbox.io/s/happy-swartz-ikqdn?file=/src/focus.js这是代码https://codesandbox.io/s/happy-swartz-ikqdn?file=/src/focus.js的链接

Note: You can go on /focus route in codesandbox's browser and open the console注意:您可以 go 在codesandbox's browser中的 /focus 路径上并打开控制台

PS: I did an increment to the counter, not the decrement. PS:我对计数器进行了增量,而不是减量。 You can do the similar by decrementing on each recursion您可以通过减少每次递归来做类似的事情

Actually it works, but you see in the console 8 every time setIntervall callback runs, because it only access to initial value of secondsLeft and it doesn't recognize the update of secondsLeft in later updates.实际上它可以工作,但是每次 setIntervall 回调运行时您都会在控制台8中看到,因为它只访问 secondsLeft 的初始值,并且在以后的更新中它不识别 secondsLeft 的更新。 You can run the below example and see it updates state everyTime setInterval callback runs but in the console you always see 0.您可以运行以下示例并查看它更新 state 每次 setInterval 回调运行,但在控制台中您总是看到 0。

 function App() { const [secondsLeft, setSeconds] = React.useState(0); let timerId = null; const handleClick = () => { setSeconds(8); timerId = setInterval(()=> { console.log(secondsLeft) setSeconds(prev => prev - 1); }, 1000) }; React.useEffect(()=> { return ()=> clearInterval(timerId) }, []) return ( <div> <h2>{secondsLeft}</h2> <button onClick={handleClick}>Start interval</button> </div> ); } ReactDOM.render(React.createElement(App, {}), document.getElementById("root"));
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script> <div id="root"></div>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM