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

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.

Related

Can I force a state update using a class as a value?

I'm interested in whether I can update the state forcibly or update the value even if it is equivalent to the previous one. I understand that in my setState() it does not work because even after calling updateAttributes() ipLocation is not considered a new value, only the information about the latitude and longitude inside was updated. Can I work around this?
// Here the `ipLocation` does not contain data on longitude and latitude.
const [ipLocation, setIpLocation] = React.useState<IpLocation>(new IpLocation());
React.useEffect(() => {
// [GET] request to the server. The server returns a value for the latitude and longitude.
ipLocation.load()
.then((response) => {
// Updating the attributes of the ipLocation, the response has all the data.
// updateAttributes() method return `this`
setIpLocation(ipLocation.updateAttributes(response.data.data));
});
}, []);
return(
<p>{ipLocation.getAttribute('latitude')}</p>
<p>{ipLocation.getAttribute('longitude')}</p>
);
This is not the way state is intended to work, as it should be immmutable: https://reactjs.org/docs/react-component.html#state
If you want to keep working with a class you could modify your code so that updateAttributes returns a new IpLocation()
But why should this be a class as you could simply store an object?
const [ipLocation, setIpLocation] = React.useState<IpLocation>({lat: 0, lng: 0});
React.useEffect(() => {
// [GET] request to the server. The server returns a value for the latitude and longitude.
ipLocation.load()
.then((response) => {
// Updating the attributes of the ipLocation, the response has all the data.
// assuming response.data.data contains an object like: { lat: 21.2323344334,lng:24.13243533}
setIpLocation({...response.data.data}));
});
}, []);
return(
<p>{ipLocation.lat}</p>
<p>{ipLocation.lng}</p>
);

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

React state late update

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

Adding objects fetched from local restify server into object in state

So I'm taking a course in web programming and in it we've gotten this assignment to design some simple front end for ordering salads, to get all the components etc. it was previously stored in a .js file in the following fashion
let inventory = {
Sallad: {price: 10, foundation: true, vegan: true},
Pasta: {price: 10, foundation: true, gluten: true},
'Salad + Pasta': {price: 10, foundation: true, gluten: true},
'Salad + Matvete': {price: 10, foundation: true, vegan: true, gluten: true},
'Kycklingfilé': {price: 10, protein: true},
'Rökt kalkonfilé': {price: 10, protein: true},
'Böngroddar': {price: 5, extra: true, vegan: true},
'Chèvreost': {price: 15, extra: true, lactose: true},
Honungsdijon: {price: 5, dressing: true, vegan: true},
Kimchimayo: {price: 5, dressing: true},
.
.
.
};
export default inventory;
This is then imported into my App.js that was created when creating the react project and sent as a prop to another component that took care of the composing of a salad that was eventually sent back to a function also sent with as a prop.
So what we're supposed to do now is to get this inventory from a local rest(?) server instead. So if I go to
http://localhost:8080/proteins
it will open a page that just displays an array with all the different choices of proteins
["Kycklingfilé","Rökt kalkonfilé","Norsk fjordlax","Handskalade räkor från Smögen","Pulled beef från Sverige","Marinerad bönmix"]
And then going to
http://localhost:8080/proteins/Kycklingfilé
Will give you another page with the properties of that ingredient
{"price":10,"protein":true}
And my attempt at recreating that inventory object with all the ingredients as properties inside state is this
class App extends Component {
constructor(props) {
super(props);
this.state = {
salads: [],
inventory: {
}
};
}
componentDidMount() {
const base = "http://localhost:8080/";
const pURL = base + "proteins/";
const fURL = base + "foundations/";
const eURL = base + "extras/";
const dURL = base + "dressings/";
fetch(fURL).then(response => response.json()).then(data => {
data.forEach(e => {
fetch(fURL + e).then(response => response.json()).then(data => {
Object.assign(this.state.inventory, {e : data})
})
})
});
fetch(pURL).then(response => response.json()).then(data => this.setState({data}));
fetch(eURL).then(response => response.json()).then(data => this.setState({data}));
fetch(dURL).then(response => response.json()).then(data => this.setState({data}));
}
I've been using
{JSON.stringify(this.state)}
to try and look at whats going on and with this code it comes out as this
{"salads":[],"inventory":{},"data":["Ceasardressing","Dillmayo","Honungsdijon","Kimchimayo","Pesto","Rhodeisland","Rostad aioli","Soyavinägrett","Örtvinägrett"]}
So the fetch works fine for getting all the ingredients of a certain type, I guess it's only the dressings since it overwrites data each time on those last three fetches. But the problem is that inventory is completely empty.
If I instead write it like this
fetch(fURL).then(response => response.json()).then(data => {
data.forEach(e => {
Object.assign(this.state.inventory, {e: fetch(fURL + e).then(response => response.json().then())})
})
});
The output becomes
{"salads":[],"inventory":{"e":{}},"data":["Ceasardressing","Dillmayo","Honungsdijon","Kimchimayo","Pesto","Rhodeisland","Rostad aioli","Soyavinägrett","Örtvinägrett"]}
So it adds the 'e' object, which is another problem since I want it to be the value of the current element, but it's completely empty, and I dont know how to get the data from that seconds fetch when I write it like that. So that's why it now looks like it does in the first code snippet, where it doesn't even get an empty 'e' inside inventory.
Finally, if I write it like that second example but just e: e like this
fetch(fURL).then(response => response.json()).then(data => {
data.forEach(e => {
Object.assign(this.state.inventory, {e: e})
})
});
The output becomes
{"salads":[],"inventory":{"e":"Salad + Quinoa"},"data":["Ceasardressing","Dillmayo","Honungsdijon","Kimchimayo","Pesto","Rhodeisland","Rostad aioli","Soyavinägrett","Örtvinägrett"]}
So it seems like everything is working up until the .forEach on the array of strings that represents a certain type of ingredient since it manages to put that into 'e' inside inventory with one of the array elements as it's value. It's only the last one in the list though but I guess that stems from the problem that it just makes the object 'e' and not the value of the current element and overwrites it for every item.
Sorry if all the rambling made the problem unclear, but what I'm trying to achieve is inventory {} inside state that looks like it did when it was in a seperate file, so that when we create the component we can send this.state.inventory instead of the imported inventory as prop. And to create that using what we can fetch from the different pages.
When you write
{e : data}
you create a new Object with a single entry. That sets the value of the key 'e' as the current value of the variable 'data'. A variable named 'e' is not involved:
const e = 'hello';
console.log(e); // "hello"
console.log({ asd: e }); // { asd: "hello" }
console.log({ e: "asd" }); // { e: "asd" }
console.log({ e: asd }); // ReferenceError: asd is not defined
What you are trying to do is using the value of the variable e as the key that you want to set. In javascript this is done using [ and ] like so:
const e = 'hello';
console.log({ [e]: "world" }); // { hello: "world" }
// this is necessery whenever you want a key that is not a simple word
console.log({ ["key with spaces"]: "world" }); // { "key with spaces": "world" }
console.log({ [e + e]: "world" }); // { hellohello: "world" }
EDIT:
there is another issue with your code above that you might encounter sooner or later:
In React you should never ever modify this.state directly. Always go through this.setState()!
https://reactjs.org/docs/state-and-lifecycle.html#using-state-correctly
In your case this is a bit more difficult, since you are making multiple requests which each affect the same key in your state (inventory).
Because you cannot know in what order the requests arrive, and whether React will actually do the setState each time new data comes, or do them all at the same time, you cannot simply use this.setState({ inventory: newInventory }). Instead you should use the function version as described here. Unfortunately this can be a bit complex to grasp in the beginning :(
in your case I would solve it like this:
fetch(fURL).then(response => response.json()).then(data => {
data.forEach(e => {
fetch(fURL + e)
.then(response => response.json())
.then(data => this.setState((prevState) => ({
inventory: Object.assign({}, prevState.inventory, {[e]: data}),
})));
})
})
});
A couple of things to note here:
note the ({ in (prevState) => ({ ... }): this is an arrow function that returns an object
we are passing a function to this.setState (see the link above for details). This function receives the current state as an argument (prevState) and should return the new State. (although it can omit keys of the old state that remain unchanged). This is better than directly passing the new state to this.setState because when multiple setState happen at the same time, React can apply the functions you pass in the right order so that all changes happen, but if you passed objects it has to decide on one of them to 'win' so changes can get lost.
In Object.assign({}, prevState.inventory, {[e]: data}), instead of modifying prevState.inventory we create a new object that contains the updated inventory. You should never modify the old state, even in this.setState.
Hope this helps :)
So with #sol's advice to use [e] to create the objects for each ingredient, this code
fetch(fURL).then(response => response.json()).then(data => {
data.forEach(e => {
fetch(fURL + [e]).then(response => response.json()).then(data => {
Object.assign(this.state.inventory, {[e] : data})
})
})
});
now works. I think why it didn't look successful with my "troubleshooting" of just printing that JSON.stringify of the entire state in render was that is just didn't render properly when react refreshed after saving the code. Updating the page makes it all blank, but clicking onto another page through a link and then back fixes it. Dont know why, but I'll take it.

Why is my array not updated in the state in React? [duplicate]

This question already has answers here:
Why is setState in reactjs Async instead of Sync?
(8 answers)
Closed 4 years ago.
After calling this.setState the talents property still holds the old value:
onTalentModalCheckboxChange(e) {
debugger;
var talent = JSON.parse(e.target.dataset.talent);
talent.checked = !talent.checked;
if (this.maximumSelectedTalentsReached() && talent.checked) return;
const talentIndex = this.state.talents.findIndex(t => t.Id == talent.Id);
let updatedTalents = [...this.state.talents];
updatedTalents[talentIndex] = talent;
this.setState({ talents: updatedTalents });
// this method (setSearchResultsTotal) uses this.state.talents,
// but the talent that is updated here, is not updated.
// It still has the 'old' checked value
this.setSearchResultsTotal();
}
The talents property contains a list of Talent objects, which all have the checked property. My problem is that when setting the updated talent object
What am I missing here?
setState is asynchronous meaning the change doesn't happen immediately. So any function that uses state, needs to be called after the setState call completes. There are two ways to do this: use componentDidUpdate to fire off the function if this.state.talents is different than prevState.talents. Or, use the setState callback to call the function:
onTalentModalCheckboxChange(e) {
debugger;
var talent = JSON.parse(e.target.dataset.talent);
talent.checked = !talent.checked;
if (this.maximumSelectedTalentsReached() && talent.checked) return;
const talentIndex = this.state.talents.findIndex(t => t.Id == talent.Id);
let updatedTalents = [...this.state.talents];
updatedTalents[talentIndex] = talent;
// Only call this.setSearchResultsTotal after state has finished updating
this.setState({ talents: updatedTalents }, () => this.setSearchResultsTotal(); );
}
this.setState({ talents: updatedTalents }, () => {
this.setSearchResultsTotal();
});
try calling ur method like this , it will work

Resources