ReactJs : Setstate is not updating the state - javascript

Below is the initial state which is assigned
state = {
movie : null,
actors : null,
directors : [],
loading : false
}
Below is the set state which i set and at the end to set state as put in comment when it is set and not set. I'm not able to access the this.state outside.
this.setState({movie:result},() => {
//then fetch the actors
const endpoint= `${API_URL}movie/${this.props.match.params.movieId}/credits?api_key=${API_KEY}`;
fetch(endpoint)
.then(result => result.json())
.then(result=>{
const directors =result.crew.filter((member)=> member.job==='Director' );
this.setState({
actors : result.cast,
directors,
loading : false
})
console.log(this.state); //here value is coming
})
})console.log(this.state); //here value is not coming
How to get this.state value with updated info?
render() {
//removed code below mull div
return (
<div className="rmdb-movie">
{this.state.movie ?
<div>
<Navigation movie={this.props.location.movieName}/>
</div>
: null
}
</div>
)
}

What you're experiencing is the result of using asynchronous functions. The callback you pass to this.setState() is called whenever the state is set, and there might be a delay between when you call this.setState() and when the state is actually updated. This means that the callback may or may not be executed when you reach the second console.log().
This explains why you are seeing these results. The first console.log() is actually called after the second console.log.

This is react lifecycle ! The setState function is not executed instantaneously. The inner console.log prints the good values because in the callback of setState you are sur it is executed. The outer console.log is executed before setState has actually modified the state and so you don't see the updated info.

What you are seeing is actually what is expected, this is because Javascript in general and your specific fetch API call is asynchronous. Your first console.log() is inside the response from the fetch request, so it is only executed when the server returns the response(which could be a few seconds or more), then the state is set, then you print using console.log. The second console.log is outside, and will actually print instantly ,even before the async API call is made, before the state has actually been updated. Therefore, I would recommend that whatever action you want to do after the state is set, should be done where the first console.log is. Or preferably like this in a callback that you can pass to this.setState to ensure the state has been updated.
this.setState({movie:result},() => {
//then fetch the actors
const endpoint= `${API_URL}movie/${this.props.match.params.movieId}/credits?api_key=${API_KEY}`;
fetch(endpoint)
.then(result => result.json())
.then(result=>{
const directors =result.crew.filter((member)=> member.job==='Director' );
this.setState({
actors : result.cast,
directors,
loading : false
},()=>{
// API Call has returned and state has been updated
// Now access this.state
});
})
})

setState operation is asynchronous, you can read more about it in react documentation.
What problem are you trying to solve? Probably you need to use hook componentDidUpdate or just use this value in next render call.

I think you should rethink the code because there is no way result will get to movies the way you put it. Except somehow you update another state inside the API function to fetch movies. So somehow you can call fetch which you must have binded to your class
this.fetch = this.fetch.bind(this) from some onclick or onchange or something.
fetch =() =>{
const endpoint= `${API_URL}movie/${this.props.match.params.movieId}/credits?api_key=${API_KEY}`;
fetch(endpoint)
.then(result => result.json())
.then(result=>{
const directors =result.crew.filter((member)=> member.job==='Director' );
this.setState({
actors : result.cast,
directors,
loading : false,
movies:result
})
})
}

Related

React - Unable to setState inside a Promise then

I'm trying to set the state inside a then() of a Promise, but the state value is not getting saved and is not accessible outside of the then().
Below is the UPDATED code:
handleSelect = location => {
this.setState({ location });
geocodeByAddress(
location
)
.then(results => getLatLng(results[0]))
.then(latLng => {
this.setState({
location_latitude: latLng.lat,
location_longitude: latLng.lng,
})
console.log(this.state.location_latitude); // returns the correct value
})
console.log(this.state.location_latitude); // returns null (the originally declared value)
}
If I console.log(this.state.location_latitude) I get a null value. But if I console.log inside the then() block, I get the correct value. How do I set the state in this case?
UPDATED explanation:
To give a better context to the whole logic here: I'm using a Google Places Autocomplete component which allows the user to select a location from the dropdown. The above method handleSelect is called when the user selects a location from the dropdown. I then extract the latitude and longitude of the place selected by the user using the geocodeByAddress method, which is a promise. I need to then retrieve the latitude and longitude and set the state accordingly, which is later used to send to the database. I can't submit the value to the database yet because there are other form inputs that the user needs to fill (which are also stored in the state) and once he clicks on Submit, I'll be submitting all the state elements in a single call to the backend. So, there is nothing I want to update with componentDidMount() or nothing is getting updated yet to use componentDidUpdate(). In any case, I can only store the state value inside the then of geocodeByAddress (please correct me if I'm wrong here). So, I HAVE to be able to modify the state value inside the then block.
The thing is when you set-state you can't see the state result immediately after that. That's why the console.log returns null. Make sure your console.log is in the render block. This is because set-state actions are asynchronous and are batched for performance gains. This is explained in documentation of setState.
Take a look at this => Why is setState in reactjs Async instead of Sync?
You need to bind the function context to current class/ component. Try something like below. Create a function like "callSetState" bind it with this and while calling in then just pass the values as parameters to it.
import React, { Component } from 'react';
class Test extends Component{
constructor() {
super()
this.callSetState = this.callSetState.bind(this);
}
callSetState(latitude, longitude) {
this.setState({
location_latitude: latitude,
location_longitude: longitude,
})
}
/* Call this func in your class and call callSetState which is already bound to your component in constructor */
// geocodeByAddress(location)
// .then(results => getLatLng(results[0]))
// .then(latLng => this.callSetState(latLng.lat, latLng.lng));
}
This is clearly an issue with your data call not react.
Your react code looks fine but I suggest spitting the code.
1) service.js file - this does your query and returns data or returns error.
2) location component, componentDidMount() or useEffect(() => {}, []) gets the data and updates state/useState()
โ€”
To give you a code snippet I need to you to provide a console log of latLang & results.
Many thanks ๐Ÿ‘Œ๐Ÿป

React await for data fetch with axios to happen before triggering next line is not working

here's a brief overview of my component OneRoadmap: in async componentDidMount, I am first calling "await this.props.getRoadmaps", which is an action in my Redux, this action will send a get request to my api which will retrieve items from my database, then, from those items retrieved, we send a dispatch to the Reducer, calling GET_ROADMAPS, this is done like
export const getRoadmaps = () => dispatch => {
dispatch(setRoadmapsLoading());
axios
.get("/api/roadmaps")
.then(res => dispatch({ type: GET_ROADMAPS, payload: res.data }));
};
and my component looks like:
async componentDidMount() {
await this.props.getRoadmaps();
var location1 = this.props.location.pathname;
var n = location1.slice(9);
var current_roadmaps = this.props.roadmap.roadmaps;
this.displayRoadmap = current_roadmaps.filter(
eachRoadmap => eachRoadmap._id == n
);
// now we have a roadmap
this.setState({
treeData: this.displayRoadmap[0].roadmap[0],
loading: false
});
}
GET_ROADMAPS will update my Redux state.
The problem appears to be: await this.props.getRoadmaps() will only wait until getRoadmaps() sends the dispatch to GET_ROADMAPS, which means it doesn't wait until GET_ROADMAPS update the redux state, this means few lines later, when I do this.props.roadmap.roadmaps, it is going to be undefined because this.props.roadmap.roadmaps is probably called before the GET_ROADMAPS finish updating my redux state.
Please guide me if there are any ways to solve to problem :) and please correct me if the problem is not what I think it is.
P.S. I referenced https://www.robinwieruch.de/react-fetching-data, this info on there will usually work but it appears to be that since I have an extra dispatch to my redux store, the dispatch updates the store after I call this.props.roadmap.roadmaps, which means I get undefined and can't use that variable in my rendering
It seems youโ€™re not returning in your action creator. You have to explicitly return when you create a block (use curly braces).
Return your axios call and it should work properly.

ReactJS: multiple setStates happening asynchronously, state not updated

Facing an issue where state is not updated, before function (filterAndSort) located in render is fired. I have added a console.log statement within the function, and only one of the states gets updated. My code seems logical to me, as I have set an if condition where function is fired only after setState occurs
Full error message:
Warning: Can't call setState (or forceUpdate) on an unmounted
component. This is a no-op, but it indicates a memory leak in your
application. To fix, cancel all subscriptions and asynchronous tasks
in the componentWillUnmount method.
However, i'm suspecting that because i'm having multiple setStates happening asynchronously.
I'm thinking that perhaps, i need to re-write the componendDidMount to setState for all variables only after all axios requests are fetched.
Multiple Axios Requests Into ReactJS State
Another solution I think would be to having the returned results of the function stored as a state instead of a variable, then add a componentDidUpdate.
componentDidUpdate(prevProps, prevState) {
if (this.state.value > prevState.value) {
this.filterAndSort();
}
}
Component
class Results extends Component {
constructor(){
super()
this.state = {
results: [],
races: [],
arr = []
isLoading: true
};
}
componentDidMount(){
const oneRequest = axios.get(URL_ONE)
.then(response =>
response.data.data.map(result => ({...
))
)
.then(results => this.setState({results, isLoading: false}))
const twoRequest = axios.get(URL_TWO)
.then(response =>
response.data.data.map(race => ({...}))
)
.then(races => this.setDefault(races))
}
setDefault = (races) => {
........
this.setState({arr, races, isLoading:false})
}
filterAndSort = (races, results) => {
console.log(races, results)
.......
}
render() {
const{races, results} = this.state
if (isLoading == true) {
return (
<div>
<p>Loading...</p>
</div>
)
} else {
return (
<div>
<BarChart
qualData={this.filterAndSort(races, results)}
raceData={this.filterAndSort(races, results)}
width="1200"
height="500" />
</div>
);
}
}
}
export default Results;
Well, there are a number of things that come to my mind.
First, you set the isLoading to false only when you have one of two pieces of data, whichever comes first, thus the render method will at some point call your function with either empty races or results.
Another thing, you are asynchronously calling setState. By the time the request is finished your component may not exist any more thus it will try to update a non existing component and fail with that error.
For the first issue, one possible solution is to have two isLoading-variables for both results and races.
For the second issue (setState called on an unmountedComponent), its a bit more complicated, because you need to somehow cancel the request. I recommend reading more on this, the general advice is to move your data out of the components using a library like redux. If you google cancel promise on unmount you will find discussion on this. You can also deal with it using an "isMounted" variable, which will work as an ugly patch.
So, as soon as request one (or two) is completed, setState is called, the component is then re-rendered. The isLoading is now true so filterAndSort is called with results (or races) but not both because the second request is still pending.
Finally, in your render method your isLoading needs to be first defined (i assume its ok in your code but not in the question) and the is True comparison can be better put as
if (isLoading) { instead of if (isLoading == True) {
You're right. When making multiple requests its always best practice to wait for them all to resolve before moving forward. For that you can use the Promise library which is built into ES6. Also, for fetching of data its best practice, so far as i've seen, to do it in componentWillMount(). I'll also add that the context of this changes when inside an async function. So in your componentWillMount():
let promiseArr = [axios.get(URL_ONE), axios.get(URL_TWO)];
let _this = this;
Promise.all(promiseArr)
.then((resp) => {
// Call _this.setState() here. Response from URL_ONE
// will be available in resp.data[0] and URL_TWO in resp.data[1]
}).catch((err) => {
console.log(err);
})
Also, in your constructor:
constructor(){
super()
this.state = {
results: [],
races: [],
arr = []
isLoading: true
};
this.setDefault = this.setDefault.bind(this);
this.filterAndSort = this.filterAndSort.bind(this);
}
Using .bind to make sure that the context of this is referrering to the current instance of the class when calling those methods.
I think after doing those things that error will go away. Hope that helps.

React Native: Strange Issue With Updating State

I am using the code below:
makeRemoteRequest = () => {
let items = [];
models.forEach(element => { //models is an array of the list of models
this.pushReports(element, items);
});
console.log("This is the item array: ",items);
this.setState({
data:items
});
console.log("This is the data in state: ",this.state.data);
}
Somehow, the console log for the items array is showing me the array that I need, but the console log for the this.state.data is empty. How can this be possible? The log for the items array is run right before state is set.
This is preventing me from updating my state.
this.setState is rendering asynchronously. And you're trying to print in next line so it will not give immediate results as you want.
Solution: do this in next line,
setTimeout(() => {console.log("This is the data in state: ",this.state.data) }, 1000)
this.setState() does not run synchronously. Your state is not guaranteed to be updated on the next immediate line, but it will be updated properly on the next render cycle. Try putting console.log within render() and you'll see.
Discussion about this topic here: https://github.com/facebook/react/issues/11527#issuecomment-360199710
Since setState works in an asynchronous way. That means after calling setState the this.state is not immediately changed. So if you want to perform an action immediately after setting state, use 2nd argument as callback on setState. Consider this example:
this.setState({
data: newData
}, () => {
//TODO: Do something with this.state here
});

Cant store data in setState react

First I check if Localstorage is supported, if it is I check if there is an item query in localstorage. If there is a item it should update to the query state. Before setState I have an alert and it alerts the item fine. But after the setState I alert again but it displays nothing? Somebody knows why? Even if I replace this.setState({query: localStorage.getItem("query")}) with this.setState({query: 'test'}) it doesnt display test in the alert?
getLocalStorage = () => {
if (typeof(Storage) !== "undefined") {
if (localStorage.getItem("query") !== null) {
//Alerts 'a string'
alert(localStorage.getItem("query"));
this.setState({
query: localStorage.getItem("query")
});
//Alerts nothing?
alert(this.state.query);
this.search();
}
else{
this.getLocation();
}
}
else {
this.getLocation();
}
};
setState won't show direct change it will take time :
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
If you want state value just after it's set you have to use it like this :
this.setState({
query: localStorage.getItem("query")
},() => {
alert(this.state.query);
});
For more details , please read setState
setState is asynchronous method so that way you are geting this kind of response. So for this bug you can see -
Is there a synchronous alternative of setState() in Reactjs

Categories

Resources