score:710
much like .setstate()
in class components created by extending react.component
or react.purecomponent
, the state update using the updater provided by usestate
hook is also asynchronous, and will not be reflected immediately.
also, the main issue here is not just the asynchronous nature but the fact that state values are used by functions based on their current closures, and state updates will reflect in the next re-render by which the existing closures are not affected, but new ones are created. now in the current state, the values within hooks are obtained by existing closures, and when a re-render happens, the closures are updated based on whether the function is recreated again or not.
even if you add a settimeout
the function, though the timeout will run after some time by which the re-render would have happened, the settimeout
will still use the value from its previous closure and not the updated one.
setmovies(result);
console.log(movies) // movies here will not be updated
if you want to perform an action on state update, you need to use the useeffect
hook, much like using componentdidupdate
in class components since the setter returned by usestate
doesn't have a callback pattern
useeffect(() => {
// action on update of movies
}, [movies]);
as far as the syntax to update state is concerned, setmovies(result)
will replace the previous movies
value in the state with those available from the async request.
however, if you want to merge the response with the previously existing values, you must use the callback syntax of state updation along with the correct use of spread syntax like
setmovies(prevmovies => ([...prevmovies, ...result]));
score:-8
// replace
return <p>hello</p>;
// with
return <p>{json.stringify(movies)}</p>;
now you should see, that your code actually does work. what does not work is the console.log(movies)
. this is because movies
points to the old state. if you move your console.log(movies)
outside of useeffect
, right above the return, you will see the updated movies object.
score:-6
use the background timer library. it solved my problem.
const timeoutid = backgroundtimer.settimeout(() => {
// this will be executed once after 1 seconds
// even when the application is the background
console.log('tac');
}, 1000);
score:-5
not saying to do this, but it isn't hard to do what the op asked without useeffect.
use a promise to resolve the new state in the body of the setter function:
const getstate = <t>(
setstate: react.dispatch<react.setstateaction<t>>
): promise<t> => {
return new promise((resolve) => {
setstate((currentstate: t) => {
resolve(currentstate);
return currentstate;
});
});
};
and this is how you use it (example shows the comparison between count
and outofsynccount
/synccount
in the ui rendering):
const app: react.fc = () => {
const [count, setcount] = usestate(0);
const [outofsynccount, setoutofsynccount] = usestate(0);
const [synccount, setsynccount] = usestate(0);
const handleonclick = async () => {
setcount(count + 1);
// doesn't work
setoutofsynccount(count);
// works
const newcount = await getstate(setcount);
setsynccount(newcount);
};
return (
<>
<h2>count = {count}</h2>
<h2>synced count = {synccount}</h2>
<h2>out of sync count = {outofsynccount}</h2>
<button onclick={handleonclick}>increment</button>
</>
);
};
score:-3
with custom hooks from my library, you can wait for the state values to update:
useasyncwatcher(...values):watcherfn(peekprevvalue: boolean)=>promise
- is a promise wrapper around useeffect that can wait for updates and return a new value and possibly a previous one if the optionalpeekprevvalue
argument is set to true.
import react, { usestate, useeffect, usecallback } from "react";
import { useasyncwatcher } from "use-async-effect2";
function testcomponent(props) {
const [counter, setcounter] = usestate(0);
const [text, settext] = usestate("");
const textwatcher = useasyncwatcher(text);
useeffect(() => {
settext(`counter: ${counter}`);
}, [counter]);
const inc = usecallback(() => {
(async () => {
await new promise((resolve) => settimeout(resolve, 1000));
setcounter((counter) => counter + 1);
const updatedtext = await textwatcher();
console.log(updatedtext);
})();
}, []);
return (
<div classname="component">
<div classname="caption">useasynceffect demo</div>
<div>{counter}</div>
<button onclick={inc}>inc counter</button>
</div>
);
}
export default testcomponent;
useasyncdeepstate
is a deep state implementation (similar to this.setstate (patchobject)) whose setter can return a promise synchronized with the internal effect. if the setter is called with no arguments, it does not change the state values, but simply subscribes to state updates. in this case, you can get the state value from anywhere inside your component, since function closures are no longer a hindrance.
import react, { usecallback, useeffect } from "react";
import { useasyncdeepstate } from "use-async-effect2";
function testcomponent(props) {
const [state, setstate] = useasyncdeepstate({
counter: 0,
computedcounter: 0
});
useeffect(() => {
setstate(({ counter }) => ({
computedcounter: counter * 2
}));
}, [state.counter]);
const inc = usecallback(() => {
(async () => {
await new promise((resolve) => settimeout(resolve, 1000));
await setstate(({ counter }) => ({ counter: counter + 1 }));
console.log("computedcounter=", state.computedcounter);
})();
});
return (
<div classname="component">
<div classname="caption">useasyncdeepstate demo</div>
<div>state.counter : {state.counter}</div>
<div>state.computedcounter : {state.computedcounter}</div>
<button onclick={() => inc()}>inc counter</button>
</div>
);
}
score:-3
var [state,setstate]=usestate(defaultvalue)
useeffect(()=>{
var updatedstate
setstate(currentstate=>{ // do not change the state by get the updated state
updatestate=currentstate
return currentstate
})
alert(updatestate) // the current state.
})
score:-3
without any addtional npm package
//...
const backendpagelisting = () => {
const [ mydata, setmydata] = usestate( {
id: 1,
content: "abc"
})
const myfunction = ( x ) => {
setpagenateinfo({
...mydata,
content: x
})
console.log(mydata) // not reflecting change immediately
let mydatanew = {...mydata, content: x };
console.log(mydatanew) // reflecting change immediately
}
return (
<>
<button onclick={()=>{ myfunction("new content")} }>update mydata</button>
</>
)
score:-2
if we have to update state only, then a better way can be if we use the push method to do so.
here is my code. i want to store urls from firebase in state.
const [imageurl, setimageurl] = usestate([]);
const [reload, setreload] = usestate(0);
useeffect(() => {
if (reload === 4) {
downloadurl1();
}
}, [reload]);
const downloadurl = async () => {
setimages([]);
try {
for (let i = 0; i < images.length; i++) {
let url = await storage().ref(urls[i].path).getdownloadurl();
imageurl.push(url);
setimageurl([...imageurl]);
console.log(url, 'check', urls.length, 'length', imageurl.length);
}
}
catch (e) {
console.log(e);
}
};
const handlesubmit = async () => {
setreload(4);
await downloadurl();
console.log(imageurl);
console.log('post submitted');
};
this code works to put urls in state as an array. this might also work for you.
score:1
i found this to be good. instead of defining state (approach 1) as, example,
const initialvalue = 1;
const [state,setstate] = usestate(initialvalue)
try this approach (approach 2),
const [state = initialvalue,setstate] = usestate()
this resolved the rerender issue without using useeffect since we are not concerned with its internal closure approach with this case.
p.s.: if you are concerned with using old state for any use case then usestate with useeffect needs to be used since it will need to have that state, so approach 1 shall be used in this situation.
score:5
the closure is not the only reason.
based on the source code of usestate
(simplified below). seems to me the value is never assigned right away.
what happens is that an update action is queued when you invoke setvalue
. and after the schedule kicks in and only when you get to the next render, these update action then is applied to that state.
which means even we don't have closure issue, react version of usestate
is not going to give you the new value right away. the new value doesn't even exist until next render.
function usestate(initialstate) {
let hook;
...
let basestate = hook.memoizedstate;
if (hook.queue.pending) {
let firstupdate = hook.queue.pending.next;
do {
const action = firstupdate.action;
basestate = action(basestate); // setvalue here
firstupdate = firstupdate.next;
} while (firstupdate !== hook.queue.pending);
hook.queue.pending = null;
}
hook.memoizedstate = basestate;
return [basestate, dispatchaction.bind(null, hook.queue)];
}
function dispatchaction(queue, action) {
const update = {
action,
next: null
};
if (queue.pending === null) {
update.next = update;
} else {
update.next = queue.pending.next;
queue.pending.next = update;
}
queue.pending = update;
ismount = false;
workinprogresshook = fiber.memoizedstate;
schedule();
}
there's also an article explaining the above in the similar way, https://dev.to/adamklein/we-don-t-know-how-react-state-hook-works-1lp8
score:6
react's useeffect has its own state/lifecycle. it's related to mutation of state, and it will not update the state until the effect is destroyed.
just pass a single argument in parameters state or leave it a black array and it will work perfectly.
react.useeffect(() => {
console.log("effect");
(async () => {
try {
let result = await fetch("/query/countries");
const res = await result.json();
let result1 = await fetch("/query/projects");
const res1 = await result1.json();
let result11 = await fetch("/query/regions");
const res11 = await result11.json();
setdata({
countries: res,
projects: res1,
regions: res11
});
} catch {}
})(data)
}, [setdata])
# or use this
useeffect(() => {
(async () => {
try {
await promise.all([
fetch("/query/countries").then((response) => response.json()),
fetch("/query/projects").then((response) => response.json()),
fetch("/query/regions").then((response) => response.json())
]).then(([country, project, region]) => {
// console.log(country, project, region);
setdata({
countries: country,
projects: project,
regions: region
});
})
} catch {
console.log("data fetch error")
}
})()
}, [setdata]);
alternatively, you can try react.useref() for instant change in the react hook.
const movies = react.useref(null);
useeffect(() => {
movies.current='values';
console.log(movies.current)
}, [])
score:9
i just finished a rewrite with usereducer, following @kentcdobs article (ref below) which really gave me a solid result that suffers not one bit from these closure problems.
see: https://kentcdodds.com/blog/how-to-use-react-context-effectively
i condensed his readable boilerplate to my preferred level of dryness -- reading his sandbox implementation will show you how it actually works.
import react from 'react'
// ref: https://kentcdodds.com/blog/how-to-use-react-context-effectively
const applicationdispatch = react.createcontext()
const applicationcontext = react.createcontext()
function statereducer(state, action) {
if (state.hasownproperty(action.type)) {
return { ...state, [action.type]: state[action.type] = action.newvalue };
}
throw new error(`unhandled action type: ${action.type}`);
}
const initialstate = {
keycode: '',
testcode: '',
testmode: false,
phonenumber: '',
resultcode: null,
mobileinfo: '',
configname: '',
appconfig: {},
};
function dispatchprovider({ children }) {
const [state, dispatch] = react.usereducer(statereducer, initialstate);
return (
<applicationdispatch.provider value={dispatch}>
<applicationcontext.provider value={state}>
{children}
</applicationcontext.provider>
</applicationdispatch.provider>
)
}
function usedispatchable(statename) {
const context = react.usecontext(applicationcontext);
const dispatch = react.usecontext(applicationdispatch);
return [context[statename], newvalue => dispatch({ type: statename, newvalue })];
}
function usekeycode() { return usedispatchable('keycode'); }
function usetestcode() { return usedispatchable('testcode'); }
function usetestmode() { return usedispatchable('testmode'); }
function usephonenumber() { return usedispatchable('phonenumber'); }
function useresultcode() { return usedispatchable('resultcode'); }
function usemobileinfo() { return usedispatchable('mobileinfo'); }
function useconfigname() { return usedispatchable('configname'); }
function useappconfig() { return usedispatchable('appconfig'); }
export {
dispatchprovider,
usekeycode,
usetestcode,
usetestmode,
usephonenumber,
useresultcode,
usemobileinfo,
useconfigname,
useappconfig,
}
with a usage similar to this:
import { usehistory } from "react-router-dom";
// https://react-bootstrap.github.io/components/alerts
import { container, row } from 'react-bootstrap';
import { useappconfig, usekeycode, usephonenumber } from '../../applicationdispatchprovider';
import { controlset } from '../../components/control-set';
import { keypadclass } from '../../utils/style-utils';
import { maskedentry } from '../../components/masked-entry';
import { messaging } from '../../components/messaging';
import { simplekeypad, handlekeypress, alt_id } from '../../components/simple-keypad';
export const altidpage = () => {
const history = usehistory();
const [keycode, setkeycode] = usekeycode();
const [phonenumber, setphonenumber] = usephonenumber();
const [appconfig, setappconfig] = useappconfig();
const keypressed = btn => {
const maxlen = appconfig.phonenumberentry.entrylen;
const newvalue = handlekeypress(btn, phonenumber).slice(0, maxlen);
setphonenumber(newvalue);
}
const dosubmit = () => {
history.push('s');
}
const disablebtns = phonenumber.length < appconfig.phonenumberentry.entrylen;
return (
<container fluid classname="text-center">
<row>
<messaging {...{ msgcolors: appconfig.pagecolors, msglines: appconfig.entrymsgs.altidmsgs }} />
</row>
<row>
<maskedentry {...{ ...appconfig.phonenumberentry, entrycolors: appconfig.pagecolors, entryline: phonenumber }} />
</row>
<row>
<simplekeypad {...{ keyboardname: alt_id, themename: appconfig.keytheme, keypressed, styleclass: keypadclass }} />
</row>
<row>
<controlset {...{ btncolors: appconfig.buttoncolors, disabled: disablebtns, btns: [{ text: 'submit', click: dosubmit }] }} />
</row>
</container>
);
};
altidpage.proptypes = {};
now everything persists smoothly everywhere across all my pages
score:15
i know that there are already very good answers. but i want to give another idea how to solve the same issue, and access the latest 'movie' state, using my module react-usestateref.
as you understand by using react state you can render the page every time the state change. but by using react ref, you can always get the latest values.
so the module react-usestateref
let you use state's and ref's together. it's backward compatible with react.usestate
, so you can just replace the import
statement
const { useeffect } = react
import { usestate } from 'react-usestateref'
const [movies, setmovies] = usestate(initialvalue);
useeffect(() => {
(async function() {
try {
const result = [
{
id: "1546514491119",
},
];
console.log("result =", result);
setmovies(result);
console.log("movies =", movies.current); // will give you the latest results
} catch (e) {
console.error(e);
}
})();
}, []);
more information:
score:435
additional details to the previous answer:
while react's setstate
is asynchronous (both classes and hooks), and it's tempting to use that fact to explain the observed behavior, it is not the reason why it happens.
tldr: the reason is a closure scope around an immutable const
value.
solutions:
read the value in render function (not inside nested functions):
useeffect(() => { setmovies(result) }, []) console.log(movies)
add the variable into dependencies (and use the react-hooks/exhaustive-deps eslint rule):
useeffect(() => { setmovies(result) }, []) useeffect(() => { console.log(movies) }, [movies])
use a temporary variable:
useeffect(() => { const newmovies = result console.log(newmovies) setmovies(newmovies) }, [])
use a mutable reference (if we don't need a state and only want to remember the value - updating a ref doesn't trigger re-render):
const moviesref = useref(initialvalue) useeffect(() => { moviesref.current = result console.log(moviesref.current) }, [])
explanation why it happens:
if async was the only reason, it would be possible to await setstate()
.
however, both props
and state
are assumed to be unchanging during 1 render.
treat
this.state
as if it were immutable.
with hooks, this assumption is enhanced by using constant values with the const
keyword:
const [state, setstate] = usestate('initial')
the value might be different between 2 renders, but remains a constant inside the render itself and inside any closures (functions that live longer even after render is finished, e.g. useeffect
, event handlers, inside any promise or settimeout).
consider following fake, but synchronous, react-like implementation:
// sync implementation:
let internalstate
let renderagain
const setstate = (updatefn) => {
internalstate = updatefn(internalstate)
renderagain()
}
const usestate = (defaultstate) => {
if (!internalstate) {
internalstate = defaultstate
}
return [internalstate, setstate]
}
const render = (component, node) => {
const {html, handleclick} = component()
node.innerhtml = html
renderagain = () => render(component, node)
return handleclick
}
// test:
const mycomponent = () => {
const [x, setx] = usestate(1)
console.log('in render:', x) // ✅
const handleclick = () => {
setx(current => current + 1)
console.log('in handler/effect/promise/settimeout:', x) // ❌ not updated
}
return {
html: `<button>${x}</button>`,
handleclick
}
}
const triggerclick = render(mycomponent, document.getelementbyid('root'))
triggerclick()
triggerclick()
triggerclick()
<div id="root"></div>
Source: stackoverflow.com
Related Query
- The useState set method is not reflecting a change immediately
- useState set method not reflecting change immediately (solution suggested in Stackoverflow not working)
- useState set method not reflecting change immediately
- useState set call not reflecting change immediately prior to first render of app
- useState set method not changing the value - React
- useState React Hook set method does not update state immediately in onChange()
- useState set method and state not change in onClick function
- useState set method inside useEffect hook not updating till refreching the page
- Why does calling the set function of a useState hook apply immediately in async functions?
- useState react hook does not set the array state
- How can I refer to the value set by useState immediately afterwards?
- React Hooks: updating state using useState does not update the state immediately
- React Hooks: using useState inside useEffect doesn't set the state immediately after first rendering
- React - useState not reflecting the updated values
- setInterval is not detecting the useState change in React
- How to re-render the component in react if useState does not change or hold the last value
- How do I change the Heroes array, to the res array? I am using the react useState hook. This has worked on previous projects but not here
- useState - do not rerender the state is set with the same object content
- I can't access the initial state of my useState hook inside of my method to change the state
- Why am I not able to set my state using useState and send the state to firebase realtime database at the same time?
- making set method of useState hook to wait for the variable update
- State not updating with useState set method
- useState hook updating without using the set method
- calling the set function from a useState using the arrow function method reloads too early
- set method in react hooks does not set the value
- react one method change two state one by one the result is not I expected
- UseState called by UseEffect doesn't update the variable using the Set method
- State set method did not effect the main object - ReactJS
- How to fix map is not function even there is already set data in the useState using React Hook
- React useState in functional components not updating the property values immediately
More Query from same tag
- A component is changing the uncontrolled value state of Select to be controlled
- Playwright e2e testing. What to do with oauth2 or email-passwordless authentication
- How can I get my component to redirect after successful action?
- How can i use React's fetch to store data into a variable?
- How can I redirect the user after REST request is resolved
- d3 line chart data point is cropped from min and max domain values
- react-google-maps merge multiple polygons
- Component responsible to display all my transactions are not updating after submit
- How to test React component without Browserify
- Problem installing react@18 - why can I not install the package?
- How to set a test for multiple fetches with Promise.all using jest
- Calendar is not displaying correctly using flatpickr
- How does React useState hook work with mutable objects
- How can change boolean type icons to oposite
- How to get the width of an element using ref in react
- Manage array from get request using useEffect and useState hooks in React
- Material dataTable
- Importing 'render' from @testing-library/react in a jsx file is erroring out
- React trigger KeyboardEvent
- How to call parent function from children in composition?
- How can I UpdateUserProfile when signing up user, in Firebase-v9, with Typescript?
- How to fix erorr, Unhandled Rejection (FirebaseError): Function DocumentReference.set() called with invalid data. Unsupported field value: undefined
- How to rotate material-ui icon
- Reactjs, sending and reading variables
- facing issue to store value in state and 3rd time state is not updating in react js
- React preserve state in custom hook
- Combine react-easy-crop with react-uploady
- How to fix the error jsx no new function as prop with typescript and react?
- React Project. Having an issue fetching data using parameter and getting infinite loops
- Reactjs how to manage multiple interactions between component