admin管理员组文章数量:1026900
I'm currently building a timer in ReactJS for practice. Currently I have two ponents, a Timer
ponent which displays the time as well as sets an interval upon mounting. I also have an App
ponent which keeps tracks of the main state, such as state for whether the timer is paused
as well as the current value of the timer
.
My goal is to make it so that when I click the pause
button, the timer stops incrementing. Currently I've tried to achieve this by using:
if(!paused)
tick(time => time+1);
which in my mind, should only increment the time
state when paused
is false
. However, when I update the paused
state by clicking on my button, this paused
state inside the setTimeout does not change. My guess that setTimeout is forming a closure over the paused
state, so it's not updating when the state changes. I tried adding paused
as a dependency to useEffect
but this caused multiple timeouts to be queued whenever paused
changed.
const {useState, useEffect} = React;
const Timer = ({time, paused, tick}) => {
useEffect(() => {
const timer = setInterval(() => {
if(!paused) // `paused` doesn't change?
tick(time => time+1);
}, 1000);
}, []);
return <p>{time}s</p>
}
const App = () => {
const [time, setTime] = useState(0);
const [paused, setPaused] = useState(false);
const togglePaused = () => setPaused(paused => !paused);
return (
<div>
<Timer paused={paused} time={time} tick={setTime} />
<button onClick={togglePaused}>{paused ? 'Play' : 'Pause'}</button>
</div>
);
}
ReactDOM.render(<App />, document.body);
<script src=".8.4/umd/react.production.min.js"></script>
<script src=".8.4/umd/react-dom.production.min.js"></script>
I'm currently building a timer in ReactJS for practice. Currently I have two ponents, a Timer
ponent which displays the time as well as sets an interval upon mounting. I also have an App
ponent which keeps tracks of the main state, such as state for whether the timer is paused
as well as the current value of the timer
.
My goal is to make it so that when I click the pause
button, the timer stops incrementing. Currently I've tried to achieve this by using:
if(!paused)
tick(time => time+1);
which in my mind, should only increment the time
state when paused
is false
. However, when I update the paused
state by clicking on my button, this paused
state inside the setTimeout does not change. My guess that setTimeout is forming a closure over the paused
state, so it's not updating when the state changes. I tried adding paused
as a dependency to useEffect
but this caused multiple timeouts to be queued whenever paused
changed.
const {useState, useEffect} = React;
const Timer = ({time, paused, tick}) => {
useEffect(() => {
const timer = setInterval(() => {
if(!paused) // `paused` doesn't change?
tick(time => time+1);
}, 1000);
}, []);
return <p>{time}s</p>
}
const App = () => {
const [time, setTime] = useState(0);
const [paused, setPaused] = useState(false);
const togglePaused = () => setPaused(paused => !paused);
return (
<div>
<Timer paused={paused} time={time} tick={setTime} />
<button onClick={togglePaused}>{paused ? 'Play' : 'Pause'}</button>
</div>
);
}
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
So, my question is:
- Why isn't my current code working (why is
paused
not updating withinuseEffect
)? - Is there any way to make the above code work so that I can "pause" my interval which is set within the
useEffect()
?
3 Answers
Reset to default 4To stop the timer return the function clearing the interval from useEffect()
.
React performs the cleanup when the ponent unmounts. However, as we learned earlier, effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time.
(source: Using the Effect Hook - Effects with Cleanup)
You should also pass the paused
in dependencies array to stop useEffect
creating new intervals on each re-render. If you add [paused]
it'll only create new interval when paused
change.
const {useState, useEffect} = React;
const Timer = ({time, paused, tick}) => {
useEffect(() => {
const timer = setInterval(() => {
if(!paused) // `paused` doesn't change?
tick(time => time+1);
}, 1000);
return () => clearInterval(timer);
}, [paused]);
return <p>{time}s</p>
}
const App = () => {
const [time, setTime] = useState(0);
const [paused, setPaused] = useState(false);
const togglePaused = () => setPaused(paused => !paused);
return (
<div>
<Timer paused={paused} time={time} tick={setTime} />
<button onClick={togglePaused}>{paused ? 'Play' : 'Pause'}</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
The setInterval captures the paused
value the first time, you'll need to remove the interval, recreate it everytime paused is changed.
You can check this article for more info: https://overreacted.io/making-setinterval-declarative-with-react-hooks
You can use Dan's useInterval
hook. I can't explain it better than him why you should use this.
The hook looks like this.
function useInterval(callback, delay) {
const savedCallback = React.useRef();
// Remember the latest callback.
React.useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
React.useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
Then use it in your Timer
ponent like this.
const Timer = ({ time, paused, tick }) => {
useInterval(() => {
if (!paused) tick(time => time + 1);
}, 1000);
return <p>{time}s</p>;
};
I believe the difference is that useInterval
hook is aware of its dependencies (paused) while setInterval
isn't.
I'm currently building a timer in ReactJS for practice. Currently I have two ponents, a Timer
ponent which displays the time as well as sets an interval upon mounting. I also have an App
ponent which keeps tracks of the main state, such as state for whether the timer is paused
as well as the current value of the timer
.
My goal is to make it so that when I click the pause
button, the timer stops incrementing. Currently I've tried to achieve this by using:
if(!paused)
tick(time => time+1);
which in my mind, should only increment the time
state when paused
is false
. However, when I update the paused
state by clicking on my button, this paused
state inside the setTimeout does not change. My guess that setTimeout is forming a closure over the paused
state, so it's not updating when the state changes. I tried adding paused
as a dependency to useEffect
but this caused multiple timeouts to be queued whenever paused
changed.
const {useState, useEffect} = React;
const Timer = ({time, paused, tick}) => {
useEffect(() => {
const timer = setInterval(() => {
if(!paused) // `paused` doesn't change?
tick(time => time+1);
}, 1000);
}, []);
return <p>{time}s</p>
}
const App = () => {
const [time, setTime] = useState(0);
const [paused, setPaused] = useState(false);
const togglePaused = () => setPaused(paused => !paused);
return (
<div>
<Timer paused={paused} time={time} tick={setTime} />
<button onClick={togglePaused}>{paused ? 'Play' : 'Pause'}</button>
</div>
);
}
ReactDOM.render(<App />, document.body);
<script src=".8.4/umd/react.production.min.js"></script>
<script src=".8.4/umd/react-dom.production.min.js"></script>
I'm currently building a timer in ReactJS for practice. Currently I have two ponents, a Timer
ponent which displays the time as well as sets an interval upon mounting. I also have an App
ponent which keeps tracks of the main state, such as state for whether the timer is paused
as well as the current value of the timer
.
My goal is to make it so that when I click the pause
button, the timer stops incrementing. Currently I've tried to achieve this by using:
if(!paused)
tick(time => time+1);
which in my mind, should only increment the time
state when paused
is false
. However, when I update the paused
state by clicking on my button, this paused
state inside the setTimeout does not change. My guess that setTimeout is forming a closure over the paused
state, so it's not updating when the state changes. I tried adding paused
as a dependency to useEffect
but this caused multiple timeouts to be queued whenever paused
changed.
const {useState, useEffect} = React;
const Timer = ({time, paused, tick}) => {
useEffect(() => {
const timer = setInterval(() => {
if(!paused) // `paused` doesn't change?
tick(time => time+1);
}, 1000);
}, []);
return <p>{time}s</p>
}
const App = () => {
const [time, setTime] = useState(0);
const [paused, setPaused] = useState(false);
const togglePaused = () => setPaused(paused => !paused);
return (
<div>
<Timer paused={paused} time={time} tick={setTime} />
<button onClick={togglePaused}>{paused ? 'Play' : 'Pause'}</button>
</div>
);
}
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
So, my question is:
- Why isn't my current code working (why is
paused
not updating withinuseEffect
)? - Is there any way to make the above code work so that I can "pause" my interval which is set within the
useEffect()
?
3 Answers
Reset to default 4To stop the timer return the function clearing the interval from useEffect()
.
React performs the cleanup when the ponent unmounts. However, as we learned earlier, effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time.
(source: Using the Effect Hook - Effects with Cleanup)
You should also pass the paused
in dependencies array to stop useEffect
creating new intervals on each re-render. If you add [paused]
it'll only create new interval when paused
change.
const {useState, useEffect} = React;
const Timer = ({time, paused, tick}) => {
useEffect(() => {
const timer = setInterval(() => {
if(!paused) // `paused` doesn't change?
tick(time => time+1);
}, 1000);
return () => clearInterval(timer);
}, [paused]);
return <p>{time}s</p>
}
const App = () => {
const [time, setTime] = useState(0);
const [paused, setPaused] = useState(false);
const togglePaused = () => setPaused(paused => !paused);
return (
<div>
<Timer paused={paused} time={time} tick={setTime} />
<button onClick={togglePaused}>{paused ? 'Play' : 'Pause'}</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
The setInterval captures the paused
value the first time, you'll need to remove the interval, recreate it everytime paused is changed.
You can check this article for more info: https://overreacted.io/making-setinterval-declarative-with-react-hooks
You can use Dan's useInterval
hook. I can't explain it better than him why you should use this.
The hook looks like this.
function useInterval(callback, delay) {
const savedCallback = React.useRef();
// Remember the latest callback.
React.useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
React.useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
Then use it in your Timer
ponent like this.
const Timer = ({ time, paused, tick }) => {
useInterval(() => {
if (!paused) tick(time => time + 1);
}, 1000);
return <p>{time}s</p>;
};
I believe the difference is that useInterval
hook is aware of its dependencies (paused) while setInterval
isn't.
本文标签: javascriptState within useEffect not updatingStack Overflow
版权声明:本文标题:javascript - State within useEffect not updating - Stack Overflow 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://it.en369.cn/questions/1745654375a2161510.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论