React state late update - reactjs

i need to develop a simple tagging system. I do the following:
- on img click I store the click position
- next I open an input text
- on input change I query db with axios
- and so on till the db insert with the tags info
The problem is that the state that I update seems to be 1 turn in the past
imgSetTag(event){
this.getTagPosition(event)
.then( (response) => {
this.setState({
'imgTagsCoord' : response
}, console.log( 'imgTagsCoord then', this.state.imgTagsCoord ));
document.getElementById("imgTagInput").focus();
})
.catch( (err) => console.log(err) )
};
getTagPosition(event){
return new Promise(function(resolve, reject) {
var bounds = event.target.getBoundingClientRect();
var x = event.clientX - bounds.left;
var y = event.clientY - bounds.top;
console.log( {x: x, y: y} );
var tagCoord = { x : Math.round( x/bounds.width * 100 ), y : Math.round( y/bounds.height * 100 ) };
resolve(tagCoord);
});
}
The last try is splitting the function in two and wrapping the second in a promise but this.state.imgTagsCoord is always i round in the past.

The second argument of setState function should be a callback function. In your case, it should be written as follows:
() => console.log('imgTagsCoord then', this.state.imgTagsCoord )
When you pass in console.log('imgTagsCoord then', this.state.imgTagsCoord ) directly (instead of wrapping it in an anonymous function), you are passing the result of the function instead of the function itself. Therefore, you get the state value at the time when the function executes, which is one round prior to what you expect.

Try to change your setState to this:
this.setState({'imgTagsCoord' : response}, () => console.log( 'imgTagsCoord then', this.state.imgTagsCoord ));
You're executing the setState callback directly, you're not giving a function that React will call once the state has been set, but you're logging directly.
The setState callback is a function that React will execute, in your code you're giving the result of the console.log execution

Related

Making an API request every X amount of seconds in reactJS

On entering CurrencyPage, I am trying to get the list of currencyConversionRates from database and then subsequently set an interval to constantly get the new updated list of currencyConversionRates every 6 seconds.
Up till this point, everything works fine. However, I am unable to pass the value of the setInterval to the cleanup function. The value being returned is always in the format of a promise
PromiseĀ {<fulfilled>: 1}
How can I pass the setInterval value to the cleanup function so that the interval stops being executed upon navigating to other pages
Use Effect
useEffect(() => {
const getCurrencyConversionRates=async()=>{
console.log("Retrieving list of currency conversion rate");
await retrieveCurrencyConversionRateFunction();
//Gets new information every 6 seconds
const intervalCall = setInterval(async() => {
console.log("Getting new currency conversion rates!");
const currTime = new Date().toLocaleTimeString();
console.log("Time -> "+currTime);
await retrieveCurrencyConversionRateFunction();
}, 60*100);
//Have to wait until currencyConversionRateFunction is loaded before the map function is called on it in the render view otherwise will have error
setLoading(false);
return intervalCall;
}
let intervalCall = getCurrencyConversionRates();
//Clear interval
return () => {
console.log("CLEARING CURRENCY INTERVAL HERE");
console.log(intervalCall);
clearInterval(intervalCall);
};
},[]);
Solution Based on Azzy's answer
Rather than to try and return the interval in getCurrencyConversionRates(), a simple alternative is to just initialize a variable to store the setInterval as it is created within the function.
Note to self -> It is not possible to pass a variable by reference through getCurrencyConversionRates() as a parameter as changes made to the variable wont be saved. However, in JS you can simply initialize the variable outside of the function and modify it's value from within the function.
https://www.javascripttutorial.net/javascript-pass-by-value/
useEffect(() => {
const getCurrencyConversionRates=async()=>{
// console.log("Retrieving list of currency conversion rate");
await retrieveCurrencyConversionRateFunction();
//Gets new information every 6 seconds
intervalCall = setInterval(async() => {
// console.log("Getting new currency conversion rates!");
const currTime = new Date().toLocaleTimeString();
console.log("Time -> "+currTime);
await retrieveCurrencyConversionRateFunction();
}, 60*100);
//Have to wait until currencyConversionRateFunction is loaded before the map function is called on it in the render view otherwise will have error
setLoading(false);
// return intervalCall;
}
let intervalCall;
getCurrencyConversionRates();
//Clear interval
return () => {
console.log("CLEARING CURRENCY INTERVAL HERE");
console.log(intervalCall);
clearInterval(intervalCall);
};
},[]);
This may help
useEffect(() => {
let isValid = true;
let intervalCall;
const getCurrencyConversionRates = async () => {
console.log("Retrieving list of currency conversion rate");
await retrieveCurrencyConversionRateFunction();
// after await if the component unmounts, and this scope
// becomes stale, skip futher execution
// so the interval wont be started, and wont break in dev mode where useEffect runs twice
if (!isValid) { return; }
//Gets new information every 6 seconds
intervalCall = setInterval( async () => {
console.log("Getting new currency conversion rates!");
const currTime = new Date().toLocaleTimeString();
console.log("Time -> "+currTime);
await retrieveCurrencyConversionRateFunction();
// might want to check valid scope inside
// retrieveCurrencyConversionRateFunction
// by passing it a flag to skip execution on a unmounted component scope
}, 60*100);
//Have to wait until currencyConversionRateFunction is loaded before the map function is called on it in the render view otherwise will have error
setLoading(false);
}
getCurrencyConversionRates();
//Clear interval
return () => {
console.log("CLEARING CURRENCY INTERVAL HERE");
isValid = false
// if interval was not stared, dont clear it
if (intervalCall) { clearInterval(intervalCall); }
console.log(intervalCall);
};
},[]);
more about useEffect life cycle, to get an idea why
A new effect is created after every render
How the cleanup for previous effect occurs before executing of current useEffect
You can read about why isValid is set synchronizing with effects
If you are intererested in taking a deep dive, consider reading a blog post by Dan on useEffect, its old but explanation the details to build a good mental model about useEffects and functional components.
Hope it helps, cheers

The useEffect won't change the state LIVE when clicked but content does change on reload

I'm trying to change my page's content when clicking on the link but the thing is it only changes on reload and not directly when clicked.
function Home( { category } ) {
const [news, setNews] = useState([]);
useEffect(() => {
async function newsArticles(){
const request = await axios.get(category);
console.log(request.data.articles);
setNews(request.data.articles);
return request;
}
newsArticles();
}, [category]);
return (
<div className='home'>
{news.map(ns => (
<NewsCard className='homeNewsCard' key = {ns.source.id} title = {ns.title} description = {ns.description} image = {ns.urlToImage} link = {ns.url}/>
))}
</div>
)
}
Since the effect captures the values at the time the component is rendered, any asynchronous code that executed in the effect will also see these values rather than the latest values. Example :
useEffect(()=>{
asyncFunc(props.val)
.then((res)=> setData(res + props.oth))
},[props.val]);
props.oth is used when the promise resolves, but the effecct captures the values of props.val and prips.oth at the time of render , the value of props.oth had changed during the time it took for the promise to reoslve , the code would see the old value
Solution :
you can either use reference to store the value of props.oth so your code will always see the latest value or you can add props.oth back to dependency array and return a cleanup function .
Example :
useEffect(()=>{
let cncl = false
asyncFunc(props.val)
.then((res)=>{
if(!cncl)
setData(res + props.oth)})
return ()=> cncl = true}
,[props.val, props.oth])
cncl flag is raised before running the effecct again (when props.val or props.oth have changes comapred with the previous render).

React native state update is not behaving as expected [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 2 years ago.
I am using the package react-native-background-downloader.
my state is initialize like this :
const [downloadFile, setDownloadFile] = useState({});
after i get data from api
i update my state:
setDownloadFile({
thumbnail:
'https://img.youtube.com/vi/J6rVaFzOEP8/maxresdefault.jpg',
heading: 'Guide To Becoming A Self-Taught Software Developer',
timing: 123,
status: 'Staring ...',
});
then i use the package to download the video from url
const ts = RNBackgroundDownloader.download({
id: inputUrl,
url: 'url too long to display',
destination: `${RNBackgroundDownloader.directories.documents}/test.mp4`,
});
setTask(ts);
ts.begin(async (res) => {
await setDownloadFile({
...downloadFile,
timing: res / 1024,
});
console.log('onbegin', downloadFile);
});
ts.progress(async (pr) => {
await setDownloadFile({
...downloadFile,
status: `Downloading... ${Math.round(pr * 100)}%`,
});
console.log('onProgress', downloadFile);
});
ts.done(async () => {
await setDownloadFile({
...downloadFile,
status: 'done',
});
console.log('onDone', downloadFile);
});
my problem is that the state update in .begin() in timing variable is not taking place in .progress()
initially => timing:123,
.begin() => timing: res / 1024,
.progress() => timing:123 (as it was in first place);
downloadFile is a local const. It will never change, and that's not what setDownloadFile tries to do. The purpose of setting state is to tell the component to rerender. On that next render, a new local variable will be created, which gets the new value.
So every time you do:
setDownloadFile({
...downloadFile
// etc
})
... you are making a copy of the downloadFile in this closure. Ie, the one that existed at the time you called RNBackgroundDownloader.download.
The simplest fix is to use the function version of setDownloadFile. You can pass a function to a state setter, and it will be called with the most recent state, and then you can base the new state on that. For example:
ts.progress((pr) => {
setDownloadFile(previous => {
return {
...previous,
status: `Downloading... ${Math.round(pr * 100)}%`,
}
});
});
I removed the async/await, because setting state does not return a promise so it served no purpose. I removed the logging too. If you want it, you'll need to put it inside the function.

how to get updated state value after updating in a function

I'm trying to get login user detail. I need to put data to state and used the same state for further processing it takes time to put data to state and use this data. nearly it takes 2- 3 sec to use the state with that data.
I solved this issue if we use setTimeOut() function with 3 sec so that we it updates data in this time. if we don't use setTimeOut() and use state wright after updating it then it will provide initial data of the state.
complete_date = date + ":" + month + ":" + year;
var query = firebase.database().ref('attendence/'+ complete_date +"/").orderByKey();
email_match=false;
entry_time =null,
l_id = null,
query.on("value",(data)=>
{
data.forEach(function(childs)
{
l_id = childs.key;
// __ to check if the user already loged in before.
var att_query = firebase.database().ref('attendence/'+ complete_date +"/"+ l_id).orderByKey();
att_query.once("value",(data)=>
{
if(email == data.child('email').val())
{
email_match = true;
entry_time = data.child('timeEntry').val();
}
}); //
}); //
this.setState({ last_id:l_id });
this.setState({ emailMatchState:email_match });
this.setState({alreayLogedState : entry_time});
}); // _____end of query.on("value",(data)=>
setTimeout(() =>
{
console.log("-----------------------------------");
console.log("already logged :",this.state.alreayLogedState," email match :",this.state.emailMatchState , " result : ", this.state.result , " last_id :",st.last_id);
console.log("-----------------------------------");
},3000);
I need to use state after updating without the use of setTimeout() for faster working of the application.
The second argument for setState is a callback to execute when all updates are done
this.setState({ last_id:l_id }, () => console.log(this.state.last_id))
setState takes an optional second argument that gets called after the updates to the state object have taken place.
example:
let callback = () => {
// do something
}
this.setState({
keyToUpdate: valueToUpdate
}, callback)
Additional reading about the use of this callback:
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296?gi=18aa91e88437
Also, unless something is happening in between the seperate calls, these:
this.setState({ last_id:l_id });
this.setState({ emailMatchState:email_match });
this.setState({alreayLogedState : entry_time});
can be simplified to this:
this.setState({
last_id:l_id,
emailMatchState: email_match,
alreayLogedState: entry_time
}); //optionally add callback

Superagent Not Returning Value From Then

superagent.get(URL).then((res) => {
for(let i in res.body) {
if (i==='has_rejected_advisories') {
console.log(i + "="+res.body[i]);
}
}
})
.catch((err) => err.message));
My result is:
has_rejected_advisories=false
But I am not able to use res.body[i] outside this function, i.e I want superagent function to return this value in a boolean variable to use it elsewhere.
ex.
a = superagent.get(URL).then((res) => {
for(let i in res.body) {
if(i==='has_rejected_advisories') {
console.log(i + "="+res.body[i]);
}
}
})
.catch((err) => err.message));
if(a===false){/*do this*/}
This is because the superagent.get(url) call is asynchronous. The value given to a is a Promise
Since this is async, the if (a === false) is actually executing before the function body passed to .then. You will either need to move this logic to the .then function, or use something like async/await if you like the synchronous looking syntax.
On top of jerelmiller's great advice you need to note the following:
Try this:
create a global var assuming it's a string
var mysares = ""
This example will only bring back 1 string back of everything!! Not single element. Also if you can't get the standard Fetch() to work don't try other methods like axios or superagents. Now use our global like so:
superagent.get(URL).then((res) => {
for(let i in res.body) {
if (i==='has_rejected_advisories') {
//Add comments as will help you
//to explain to yourself and others
//what you're trying to do
//That said if this iteration produces
//correct data then you're fine
//push my result as a string
mysares = res.body[i];
//infact what's in row 1?
mysares = res.body[0];
//Actually I code my own JSON!!
mysares = res.body[1];
console.log(i + "="+mysares);
}
}
})
.catch((err) => err.message));
Now you can do whatever:
if(mysares===false){/*do this*/
alert(playDuckHunt());}
Things to note:
res.body[i] is an iteration
You cannot use it outside of the function
Because:
It's local to that function
You don't know what position of 'i' is even if you could use it as you will be outside of your loop
One last thing:
Loops loop through loops or arrays etc.
So (in real world) you can't just request the value of the loop
unless you agree the position of data to be released,
type,and bucket (where it's going to be displayed or not).
Hope this helps!
PS> we need to know where 'has_rejected_advisories' is in the JSON so send us your json url as it must be a column/obj header name. Or it's any old 'a' then var a can be your "false"
In constructor:
this.state = {a:null};
In some function:
superagent.get(URL).then(
(res) => {for(let i in res.body)
{
if(i === 'has_rejected_advisories')
{
this.setState({a:res.body[i]})
}
}
}).catch((err)=>(err.message));
In render:
console.log(this.state.a);
Inside then() the value could be used using state variable but there are many scenarios we could not use them, like if we want to perform all the operations under constructor i.e Initializing state variable, calling superagent and changing the state variable and using the state variable.

Resources