score:254
the reason is because the callback passed into setinterval
's closure only accesses the time
variable in the first render, it doesn't have access to the new time
value in the subsequent render because the useeffect()
is not invoked the second time.
time
always has the value of 0 within the setinterval
callback.
like the setstate
you are familiar with, state hooks have two forms: one where it takes in the updated state, and the callback form which the current state is passed in. you should use the second form and read the latest state value within the setstate
callback to ensure that you have the latest state value before incrementing it.
bonus: alternative approaches
dan abramov, goes in-depth into the topic about using
setinterval
with hooks in his blog post and provides alternative ways around this issue. highly recommend reading it!
function clock() {
const [time, settime] = react.usestate(0);
react.useeffect(() => {
const timer = window.setinterval(() => {
settime(prevtime => prevtime + 1); // <-- change this line!
}, 1000);
return () => {
window.clearinterval(timer);
};
}, []);
return (
<div>seconds: {time}</div>
);
}
reactdom.render(<clock />, document.queryselector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
score:-1
tell react re-render when time changed.opt out
function clock() {
const [time, settime] = react.usestate(0);
react.useeffect(() => {
const timer = window.setinterval(() => {
settime(time + 1);
}, 1000);
return () => {
window.clearinterval(timer);
};
}, [time]);
return (
<div>seconds: {time}</div>
);
}
reactdom.render(<clock />, document.queryselector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
score:0
do as below it works fine.
const [count , setcount] = usestate(0);
async function increment(count,value) {
await setcount(count => count + 1);
}
//call increment function
increment(count);
score:0
i copied the code from this blog. all credits to the owner. https://overreacted.io/making-setinterval-declarative-with-react-hooks/
the only thing is that i adapted this react code to react native code so if you are a react native coder just copy this and adapt it to what you want. is very easy to adapt it!
import react, {usestate, useeffect, useref} from "react";
import {text} from 'react-native';
function counter() {
function useinterval(callback, delay) {
const savedcallback = useref();
// remember the latest function.
useeffect(() => {
savedcallback.current = callback;
}, [callback]);
// set up the interval.
useeffect(() => {
function tick() {
savedcallback.current();
}
if (delay !== null) {
let id = setinterval(tick, delay);
return () => clearinterval(id);
}
}, [delay]);
}
const [count, setcount] = usestate(0);
useinterval(() => {
// your custom logic here
setcount(count + 1);
}, 1000);
return <text>{count}</text>;
}
export default counter;
score:0
const [loop, setloop] = usestate(0);
useeffect(() => {
setinterval(() => setloop(math.random()), 5000);
}, []);
useeffect(() => {
// do something...
}, [loop])
score:0
for those looking for a minimalist solution for:
- stop interval after n seconds, and
- be able to reset it multiple times again on button click.
(i am not a react expert by any means my coworker asked to help out, i wrote this up and thought someone else might find it useful.)
const [disabled, setdisabled] = usestate(true)
const [inter, setinter] = usestate(null)
const [seconds, setseconds] = usestate(0)
const startcounting = () => {
setseconds(0)
setdisabled(true)
setinter(window.setinterval(() => {
setseconds(seconds => seconds + 1)
}, 1000))
}
useeffect(() => {
startcounting()
}, [])
useeffect(() => {
if (seconds >= 3) {
setdisabled(false)
clearinterval(inter)
}
}, [seconds])
return (<button style = {{fontsize:'64px'}}
onclick={startcounting}
disabled = {disabled}>{seconds}</button>)
}
score:1
this solutions dont work for me because i need to get the variable and do some stuff not just update it.
i get a workaround to get the updated value of the hook with a promise
eg:
async function getcurrenthookvalue(sethookfunction) {
return new promise((resolve) => {
sethookfunction(prev => {
resolve(prev)
return prev;
})
})
}
with this i can get the value inside the setinterval function like this
let datefrom = await getcurrenthackvalue(setselecteddatefrom);
score:1
function clock() {
const [time, settime] = react.usestate(0);
react.useeffect(() => {
const timer = window.setinterval(() => {
settime(time => time + 1);// **set callback function here**
}, 1000);
return () => {
window.clearinterval(timer);
};
}, []);
return (
<div>seconds: {time}</div>
);
}
reactdom.render(<clock />, document.queryselector('#app'));
score:1
const [seconds, setseconds] = usestate(0);
useeffect(() => {
const interval = setinterval(() => {
setseconds((seconds) => {
if (seconds === 5) {
setseconds(0);
return clearinterval(interval);
}
return (seconds += 1);
});
}, 1000);
}, []);
note: this will help to update and reset the counter with usestate hook. seconds will stop after 5 seconds. because first change setsecond value then stop timer with updated seconds within setinterval. as useeffect run once.
score:1
somehow similar issue, but when working with a state value which is an object and is not updating.
i had some issue with that so i hope this may help someone. we need to pass the older object merged with the new one
const [data, setdata] = usestate({key1: "val", key2: "val"});
useeffect(() => {
setdata(...data, {key2: "new val", newkey: "another new"}); // --> pass old object
}, []);
score:9
an alternative solution would be to use usereducer
, as it will always be passed the current state.
function clock() {
const [time, dispatch] = react.usereducer((state = 0, action) => {
if (action.type === 'add') return state + 1
return state
});
react.useeffect(() => {
const timer = window.setinterval(() => {
dispatch({ type: 'add' });
}, 1000);
return () => {
window.clearinterval(timer);
};
}, []);
return (
<div>seconds: {time}</div>
);
}
reactdom.render(<clock />, document.queryselector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
score:12
useref can solve this problem, here is a similar component which increase the counter in every 1000ms
import { usestate, useeffect, useref } from "react";
export default function app() {
const initalstate = 0;
const [count, setcount] = usestate(initalstate);
const counterref = useref(initalstate);
useeffect(() => {
counterref.current = count;
})
useeffect(() => {
setinterval(() => {
setcount(counterref.current + 1);
}, 1000);
}, []);
return (
<div classname="app">
<h1>the current count is:</h1>
<h2>{count}</h2>
</div>
);
}
and i think this article will help you about using interval for react hooks
score:30
useeffect
function is evaluated only once on component mount when empty input list is provided.
an alternative to setinterval
is to set new interval with settimeout
each time the state is updated:
const [time, settime] = react.usestate(0);
react.useeffect(() => {
const timer = settimeout(() => {
settime(time + 1);
}, 1000);
return () => {
cleartimeout(timer);
};
}, [time]);
the performance impact of settimeout
is insignificant and can be generally ignored. unless the component is time-sensitive to the point where newly set timeouts cause undesirable effects, both setinterval
and settimeout
approaches are acceptable.
score:40
as others have pointed out, the problem is that usestate
is only called once (as deps = []
) to set up the interval:
react.useeffect(() => {
const timer = window.setinterval(() => {
settime(time + 1);
}, 1000);
return () => window.clearinterval(timer);
}, []);
then, every time setinterval
ticks, it will actually call settime(time + 1)
, but time
will always hold the value it had initially when the setinterval
callback (closure) was defined.
you can use the alternative form of usestate
's setter and provide a callback rather than the actual value you want to set (just like with setstate
):
settime(prevtime => prevtime + 1);
but i would encourage you to create your own useinterval
hook so that you can dry and simplify your code by using setinterval
declaratively, as dan abramov suggests here in making setinterval declarative with react hooks:
function useinterval(callback, delay) {
const intervalref = react.useref();
const callbackref = react.useref(callback);
// remember the latest callback:
//
// without this, if you change the callback, when setinterval ticks again, it
// will still call your old callback.
//
// if you add `callback` to useeffect's deps, it will work fine but the
// interval will be reset.
react.useeffect(() => {
callbackref.current = callback;
}, [callback]);
// set up the interval:
react.useeffect(() => {
if (typeof delay === 'number') {
intervalref.current = window.setinterval(() => callbackref.current(), delay);
// clear interval if the components is unmounted or the delay changes:
return () => window.clearinterval(intervalref.current);
}
}, [delay]);
// returns a ref to the interval id in case you want to clear it manually:
return intervalref;
}
const clock = () => {
const [time, settime] = react.usestate(0);
const [ispaused, setpaused] = react.usestate(false);
const intervalref = useinterval(() => {
if (time < 10) {
settime(time + 1);
} else {
window.clearinterval(intervalref.current);
}
}, ispaused ? null : 1000);
return (<react.fragment>
<button onclick={ () => setpaused(previspaused => !previspaused) } disabled={ time === 10 }>
{ ispaused ? 'resume ⏳' : 'pause 🚧' }
</button>
<p>{ time.tostring().padstart(2, '0') }/10 sec.</p>
<p>setinterval { time === 10 ? 'stopped.' : 'running...' }</p>
</react.fragment>);
}
reactdom.render(<clock />, document.queryselector('#app'));
body,
button {
font-family: monospace;
}
body, p {
margin: 0;
}
p + p {
margin-top: 8px;
}
#app {
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
button {
margin: 32px 0;
padding: 8px;
border: 2px solid black;
background: transparent;
cursor: pointer;
border-radius: 2px;
}
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
apart from producing simpler and cleaner code, this allows you to pause (and clear) the interval automatically by simply passing delay = null
and also returns the interval id, in case you want to cancel it yourself manually (that's not covered in dan's posts).
actually, this could also be improved so that it doesn't restart the delay
when unpaused, but i guess for most uses cases this is good enough.
if you are looking for a similar answer for settimeout
rather than setinterval
, check this out: https://stackoverflow.com/a/59274757/3723993.
you can also find declarative version of settimeout
and setinterval
, usetimeout
and useinterval
, a few additional hooks written in typescript in https://www.npmjs.com/package/@swyg/corre.
Source: stackoverflow.com
Related Query
- State not updating when using React state hook within setInterval
- setInterval updating state in React but not recognizing when time is 0
- State variable inside a function defined within useEffect hook is not updating when value changes
- Value of state within React context not updating when used in another component
- State is not updating when using React form. I am trying to use the user input to display an order confirmation in a modal
- React not updating its state when come back using react-router in Apollo Client
- React state not updated correctly when using useState hook
- React state not updating when used outside hook
- React hook not updating the state within the hook
- React Redux state not updating when using non mutable methods
- Updating and merging state object using React useState() hook
- React component not updating when store state has changed
- Can not update state inside setInterval in react hook
- Props not updating when redux state change in React Hooks
- React Hook useCallback not updating State value
- React is not updating when Redux state is changed below the first level
- Prevent infinite loop when updating state via React useEffect Hook
- My state is not updating using React Hooks
- React useState() hook does not update state when child.shouldComponentUpdate() returns false
- React setState() not updating state when button clicked
- Reading component state just after setting when using useState hook in react
- React Hooks: state not updating when called inside Socket.io handler
- React DnD useDrop is not using the current state when the method is called
- React component not re-rendering on URL parameter change when using useEffect hook to fetch data
- The state variable returned after using useState react hook shows .map is not a function
- React custom hook with event listener not works while updating the state
- State not updating when using select and options
- react / redux toolkit useSelector not updating when store state changed
- React Hooks: updating state using useState does not update the state immediately
- HighCharts not updating when the state changes in React
More Query from same tag
- How add style for child elements with react-jss component?
- How to design a UI Component like this in React?
- Fetch data from PostgreSQL into React
- npm ERR! missing script: jasmine
- ReactJs browser Cannot read property 'keys' of undefined
- Advanced conditional component rendering in react
- Trouble rendering objects from Axios call after using React hook
- How to change i18next default language in reactjs
- Using local storage in ReactJS
- How to add fields to array that have no property react js
- When I add setState I get error message about unexpected token
- React Dev Tools: "parse hook names" action throws a "Hook parsing failed" error
- How to translate the following jQuery code to React?
- Request to api works fine in component but not when using provider with react
- React Js get params from URL
- How to animate element height in React with ReactCSSTransitionGroup?
- Difference between import React and import { Component } syntax
- Preload all pictures in create-react-app
- Can you / how do you add a constructor into an object?
- Dynamic filter operator in GraphQL using Apollo (in a React app)
- React PayPal Checkout
- React set in useState only running once
- How to render value in promises on button click in react?
- How to select nested object's property with map() in Javascript?
- Issue with props in react.js
- proxying from containerized production react to containerized flask
- "TypeError: Cannot read property 'preventDefault' of undefined" in react js
- Making a table with a checkbox that updates table values in React
- React app for cordova shows white screen for android
- Enzyme unit test is passing test when it should fail