score:176
for your scenario (where you cannot keep creating new callbacks and passing them to your 3rd party library), you can use useref
to keep a mutable object with the current state. like so:
function card(title) {
const [count, setcount] = react.usestate(0)
const [callbacksetup, setcallbacksetup] = react.usestate(false)
const stateref = useref();
// make stateref always have the current count
// your "fixed" callbacks can refer to this object whenever
// they need the current value. note: the callbacks will not
// be reactive - they will not re-run the instant state changes,
// but they *will* see the current value whenever they do run
stateref.current = count;
function setupconsolecallback(callback) {
console.log("setting up callback")
setinterval(callback, 3000)
}
function clickhandler() {
setcount(count+1);
if (!callbacksetup) {
setupconsolecallback(() => {console.log(`count is: ${stateref.current}`)})
setcallbacksetup(true)
}
}
return (<div>
active count {count} <br/>
<button onclick={clickhandler}>increment</button>
</div>);
}
your callback can refer to the mutable object to "read" the current state. it will capture the mutable object in its closure, and every render the mutable object will be updated with the current state value.
score:0
i would use a combination of setinterval()
and useeffect()
.
setinterval()
on its own is problematic, as it might pop after the component has been unmounted. in your toy example this is not a problem, but in the real world it's likely that your callback will want to mutate your component's state, and then it would be a problem.useeffect()
on its own isn't enough to cause something to happen in some period of time.useref()
is really for those rare occasions where you need to break react's functional model because you have to work with some functionality that doesn't fit (e.g. focusing an input or something), and i would avoid it for situations like this.
your example isn't doing anything very useful, and i'm not sure whether you care about how regular the timer pops are. so the simplest way of achieving roughly what you want using this technique is as follows:
import react from 'react';
const interval_for_timer_ms = 3000;
export function card({ title }) {
const [count, setcount] = react.usestate(0)
react.useeffect(
() => {
const intervalid = setinterval(
() => console.log(`count is ${count}`),
interval_for_timer_ms,
);
return () => clearinterval(intervalid);
},
// you only want to restart the interval when count changes
[count],
);
function clickhandler() {
// i would also get in the habit of setting this way, which is safe
// if the increment is called multiple times in the same callback
setcount(num => num + 1);
}
return (
<div>
active count {count} <br/>
<button onclick={clickhandler}>increment</button>
</div>
);
}
the caveat is that if the timer pops, then you click a second later, then the next log will be 4 seconds after the previous log because the timer is reset when you click.
if you want to solve that problem, then the best thing will probably be to use date.now()
to find the current time and use a new usestate()
to store the next pop time you want, and use settimeout()
instead of setinterval()
.
it's a bit more complicated as you have to store the next timer pop, but not too bad. also that complexity can be abstracted by simply using a new function. so to sum up here's a safe "reacty" way of starting a periodic timer using hooks.
import react from 'react';
const interval_for_timer_ms = 3000;
const useinterval = (func, period, deps) => {
const [nextpoptime, setnextpoptime] = react.usestate(
date.now() + period,
);
react.useeffect(() => {
const timerid = settimeout(
() => {
func();
// setting nextpoptime will cause us to run the
// useeffect again and reschedule the timeout
setnextpoptime(poptime => poptime + period);
},
math.max(nextpoptime - date.now(), 0),
);
return () => cleartimeout(timerid);
}, [nextpoptime, ...deps]);
};
export function card({ title }) {
const [count, setcount] = react.usestate(0);
useinterval(
() => console.log(`count is ${count}`),
interval_for_timer_ms,
[count],
);
return (
<div>
active count {count} <br/>
<button onclick={() => setcount(num => num + 1)}>
increment
</button>
</div>
);
}
and as long as you pass all the dependencies of the interval function in the deps
array (exactly like with useeffect()
), you can do whatever you like in the interval function (set state etc.) and be confident nothing will be out of date.
score:0
onclick={() => { clickhandler(); }}
this way you run the function as defined when you click it not when you declared the onclick handler.
react re-runs the hook function every time there is a change, and when it does so it re-defines your clickhandler()
function.
for the record, you could clean that up. since you don't care what your arrow function returns you could write it as such.
onclick={e => clickhandler()}
score:1
i had similar problem but in my case i was using redux with hooks and also i was not mutating state in the same component. i had method which on callback used state value score
and it (callback) had old score.
so my sollution for that is quite simple. it's not as ellegant as previous ones but in my case it did the job so i'm putting it here with hope that it will help someone.
const score = useselector((state) => state.score);
const scoreref = useref(score);
useeffect(() => {
scoreref.current = score ;
}, [score])
general idea is to store newest state in ref :)
score:2
i really like @davnicwil's answer and hopefully with the source code of usestate
, it might be more clear what he meant.
// once when the component is mounted
constructor(initialvalue) {
this.args = object.freeze([initialvalue, this.updater]);
}
// every time the component is invoked
update() {
return this.args
}
// every time the setstate is invoked
updater(value) {
this.args = object.freeze([value, this.updater]);
this.el.update()
}
in the usage, if initialvalue
starts as a number or string, ex. 1.
const component = () => {
const [state, setstate] = usestate(initialvalue)
}
walkthrough
- first time you run
usestate
,this.args
= [1, ] - second time you run
usestate
,this.args
no change - if
setstate
is invoked with 2,this.args
= [2, ] - next time you run
usestate
,this.args
no change
now, if you do something especially with a deferred usage of the value.
function dosomething(v) {
// i need to wait for 10 seconds
// within this period of time
// ui has refreshed multiple times
// i'm in step 4)
console.log(v)
}
// invoke this function in step 1)
dosomething(value)
you will be given an "old" value, because you pass the current copy (at that time) to it at the first place. although this.args
gets the latest copy every time, it doesn't mean an old copy get changed. the value you passed isn't reference based. this could be a feature!!
summary
in order to change it,
- use the value without passing it;
- use an
object
for the value; - use
useref
to getlatest
value; - or design another hook.
although the approach above all fix it (in other answers), the root cause of the problem is that you pass an old value to a function and expect it to run with a future value. i think this is where it went wrong at the first place, which isn't very clear if you just look at the solutions.
score:3
the only method i know is to call the setstate(current_value => ...) function and use the current_value in ur logic. just make sure you return it back. ex:
const mypollingfunction = () => {
setinterval(() => {
setstate(latest_value => {
// do something with latest_value
return latest_value;
}
}, 1000);
};
score:4
instead of trying to access the most recent state within a callback, use useeffect
. setting your state with the function returned from setstate
will not immediately update your value. the state updates are batched and updated
it may help if you think of useeffect()
like setstate
's second parameter (from class based components).
if you want to do an operation with the most recent state, use useeffect()
which will be hit when the state changes:
const {
usestate,
useeffect
} = react;
function app() {
const [count, setcount] = usestate(0);
const decrement = () => setcount(count-1);
const increment = () => setcount(count+1);
useeffect(() => {
console.log("useeffect", count);
}, [count]);
console.log("render", count);
return (
<div classname="app">
<p>{count}</p>
<button onclick={decrement}>-</button>
<button onclick={increment}>+</button>
</div>
);
}
const rootelement = document.getelementbyid("root");
reactdom.render( < app / > , rootelement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>
update
you can create a hook for your setinterval
and call it like this:
const {
usestate,
useeffect,
useref
} = react;
function useinterval(callback, delay) {
const savedcallback = useref();
// remember the latest callback.
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]);
}
function card(title) {
const [count, setcount] = usestate(0);
const callbackfunction = () => {
console.log(count);
};
useinterval(callbackfunction, 3000);
useeffect(()=>{
console.log('count has been updated!');
}, [count]);
return (<div>
active count {count} <br/>
<button onclick={()=>setcount(count+1)}>increment</button>
</div>);
}
const el = document.queryselector("#root");
reactdom.render(<card title='example component'/>, el);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>
score:4
you can access the latest state
in setstate
callback. but the intention is not clear, we never want to setstate
in this case, it may confuse other people when they read your code. so you may want to wrap it in another hook that can express what you want better
function useextendedstate<t>(initialstate: t) {
const [state, setstate] = react.usestate<t>(initialstate);
const getlateststate = () => {
return new promise<t>((resolve, reject) => {
setstate((s) => {
resolve(s);
return s;
});
});
};
return [state, setstate, getlateststate] as const;
}
usage
const [counter, setcounter, getcounter] = useextendedstate(0);
...
getcounter().then((counter) => /* ... */)
// you can also use await in async callback
const counter = await getcounter();
live demo
score:30
i encountered a similar bug trying to do exactly the same thing you're doing in your example - using a setinterval
on a callback that references props
or state
from a react component.
hopefully i can add to the good answers already here by coming at the problem from a slightly different direction - the realisation that it's not even a react problem, but a plain old javascript problem.
i think what catches one out here is thinking in terms of the react hooks model, where the state variable, just a local variable after all, can be treated as though it's stateful within the context of the react component. you can be sure that at runtime, the value of the variable will always be whatever react is holding under the hood for that particular piece of state.
however, as soon as you break out of the react component context - using the variable in a function inside a setinterval
for instance, the abstraction breaks and you're back to the truth that that state variable really is just a local variable holding a value.
the abstraction allows you to write code as if the value at runtime will always reflect what's in state. in the context of react, this is the case, because what happens is whenever you set the state the entire function runs again and the value of the variable is set by react to whatever the updated state value is. inside the callback, however, no such thing happens - that variable doesn't magically update to reflect the underlying react state value at call time. it just is what it is when the callback was defined (in this case 0
), and never changes.
here's where we get to the solution: if the value pointed to by that local variable is in fact a reference to a mutable object, then things change. the value (which is the reference) remains constant on the stack, but the mutable value(s) referenced by it on the heap can be changed.
this is why the technique in the accepted answer works - a react ref provides exactly such a reference to a mutable object. but i think it's really important to emphasise that the 'react' part of this is just a coincidence. the solution, like the problem, has nothing to do with react per-se, it's just that a react ref happens to be one way to get a reference to a mutable object.
you can also use, for instance, a plain javascript class, holding its reference in react state. to be clear, i'm not suggesting this is a better solution or even advisable (it probably isn't!) but just using it to illustrate the point that there is no 'react' aspect to this solution - it's just javascript:
class count {
constructor (val) { this.val = val }
get () { return this.val }
update (val) {
this.val += val
return this
}
}
function card(title) {
const [count, setcount] = react.usestate(new count(0))
const [callbacksetup, setcallbacksetup] = react.usestate(false)
function setupconsolecallback(callback) {
console.log("setting up callback")
setinterval(callback, 3000)
}
function clickhandler() {
setcount(count.update(1));
if (!callbacksetup) {
setupconsolecallback(() => {console.log(`count is: ${count.get()}`)})
setcallbacksetup(true)
}
}
return (
<div>
active count {count.get()} <br/>
<button onclick={clickhandler}>increment</button>
</div>
)
}
const el = document.queryselector("#root");
reactdom.render(<card title='example component' />, el);
you can see there that simply by having the state point to a reference, that doesn't change, and mutating the underlying values that the reference points to, you get the behaviour you're after both in the setinterval
closure and in the react component.
again, this is not idiomatic react, but just illustrates the point about references being the ultimate issue here. hope it's helpful!
score:53
update june 2021:
use the npm module react-usestateref
to get always the latest state value. it's fully backward compatible with react usestate
api.
example code how to use it:
import usestate from 'react-usestateref';
const [count, setcount, counterref] = usestate(0);
console.log(couterref.current); // it will always have the latest state value
setcount(20);
console.log(counterref.current);
the npm package react-usestateref lets you access the latest state (like ref
), by using usestate
.
update dec 2020:
to solve exactly this issue i have created a react module for that. react-usestateref
(react usestateref). e.g. of use:
var [state, setstate, ref] = usestate(0);
it's works exaclty like usestate
but in addition, it gives you the current state under ref.current
learn more:
original answer
you can get the latest value by using the setstate
for example:
var [state, setstate] = usestate(defaultvalue);
useeffect(() => {
var updatedstate;
setstate(currentstate => { // do not change the state by getting the updated state
updatestate = currentstate;
return currentstate;
})
alert(updatestate); // the current state.
})
Source: stackoverflow.com
Related Query
- React hooks: accessing up-to-date state from within a callback
- React hooks not returning updated state values inside callback function from library events (FabricJS)
- React Hooks accessing state from useState before it's declared
- React hooks state is not the latest when is used from a non-react callback
- How do I access the Context and Component state from within a DOM callback in a function React component?
- Accessing a part of reducer state from one reducer within another reducer
- React get state from Redux store within useEffect
- Accessing a reducer state from within another reducer
- Accessing the State of a Functional Component with React Hooks when Testing with Enzyme
- React hooks - remove from react-router location state when we refresh page
- Accessing the state from within a redux-observable epic
- How to compare values from react redux state in hooks
- Accessing React state from outside
- React JS React Query's useMutation: Unable to retrieve the response from the server within the onError callback
- Updating React component state from callback
- How to set state in react hooks only if state is different from current value
- How do I correctly add data from multiple endpoints called inside useEffect to the state object using React Hooks and Context API?
- Remove object by id from array in React hooks state
- React state always returning previous (or initial) state inside callback from fabricjs
- How to pass state value to a callback function using React Hooks
- React accessing state from a dynamically generated form, multiple levels deep
- Problem reading state from React child Components using child->parent callback register model
- React accessing custom state field from location.state
- React Hooks useEffect to call a prop callback from useCallback
- React useState Hooks - managing multiple loading states within a state
- Not able to set state in React Hooks using data received from Axios
- Referencing state from within state - React js
- React Hooks - What is the recommended way to initialize state variables with useState() from props
- Is there a way of passing state information from react hooks into nested functions in react?
- Accessing a property from an array of objects stored in state in React
More Query from same tag
- trying to update state when clicked
- Can't add items to material-ui form
- react page showing allrecordings.map is not a function
- mapping prop items in an array in React
- React Component State Being Reset to Default
- React: How to get loading spinner to show whilst API data loads into my chart.js chart
- Struggling with 'what goes where' in react/redux/saga/selector setup
- How to add defaultProps to a generic functional component with component injection in TypeScript?
- How do I change the bar colors on Ant Design Chart
- Why does the wrapper div only take some part of the page when it has all the components in it?
- How to get input on button click with reactstrap
- `No "exports" main resolved ` error when trying to run Storybook
- Create new array depending of the number of properties in another object in Javascript
- i18next backendConnector: loading namespace translations for language fr failed
- React Hooks: how to prevent re-rendering the whole component when only the props.children value changes?
- redux state is undefined in useEffect and useSelector
- React useState causes render loop
- Trouble calling useStyles in components (Material-UI)
- Populate react select options array with key value pairs from firebase collection
- how to test a useEffect inside custom hook that create a script tag dynamically
- Dynamically loading Markdown file in React
- Problem with refreshing values in redux reducer. Why can't I create action and assign new values of state?
- How to show results after selecting a product in React
- How can I get the onChange method to get the value of a dynamically rendered Select?
- How to perform search on data that is stored in context api in reactjs?
- run my method when redux change store props. ReactJS
- React prop types for custom hook
- React JS - using back button to get previous searches
- React is not updating the passing prop
- The first card of my React Bootstrap's accordion does not open