Reputation: 484
I am trying to do a sequence of fetching some API data and manipulating it as follows:
I am having some trouble with getting the timing with all these asynchronous activities right (I am pretty new to Javascript).
Here's what I have so far:
class Map extends Component {
constructor(props) {
super(props);
this.state = {
addresses = [],
geocodedAddresses = []
}
}
componentDidMount() {
const geocodedAddresses = []
fetch('.....')
.then(result => result.json())
.then(addresses => this.setState({ addresses }, function() {
this.state.addresses.forEach(address => {
Geocode.fromAddress(address).then(geocodedAddress => {
geocodedAddresses.push(geocodedAddress['results'][0]['geometry']['location'])
})
})
}))
console.log(geocodedAddresses) //Correctly contains the geocoded addresses
this.setState({ geocodedAddresses })
}
}
render() {
this.state.addresses.map(a => console.log(a)) //Evaluates correctly
this.state.geocodedAddresses.map(ga => console.log(ga)) //Yields nothing....
.....
}
}
So I don't quite understand why React does not re render when I do this.setState({ geocodedAddresses })
- Shouldn't react re render when I do setState?
Upvotes: 0
Views: 73
Reputation: 6335
There are a couple errors with your code. In the first place, the state object is being created with equals instead of colons:
this.state = {
addresses: [],
geocodedAddresses: []
}
In the second place, you should take into account that your code is asynchronous. Only when the promises generated by the call to Geocode.fromAddress
resolve you have the data for your geocodedAddresses
.
In the componentDidMount
, you are console logging the geocodedAdresses
and you report that you are seeing the right values. This is only because the log is update after the promises resolve. But when you do the console.log
the value at that moment for geocodedAdresses
is an empty array. And that is the value that is being inserted in the component state
.
In order to set the correct value for the state, you should call setState
when all your Geocode.fromAddress
promises have resolved. In order to do that you can use Promise.all method.
So, your code would look like:
class Map extends Component {
constructor(props) {
super(props);
this.state = {
addresses: [],
geocodedAddresses: []
}
}
componentDidMount() {
fetch('.....')
.then(result => result.json())
.then(addresses => {
Promise.all(addresses.map(address => Geocode.fromAddress(address)))
.then(geocodedAddresses => {
this.setState({
addresses,
geocodedAddresses
})
});
}))
}
}
render() {
this.state.addresses.map(a => console.log(a)) //Evaluates correctly
this.state.geocodedAddresses.map(ga => console.log(ga)) //Yields nothing....
.....
}
}
Note that with this solution setState
is only being called once.
Since all your state
refers to addresses information, it could make sense to merge that information into a single key in the state
. You could initialize your state
in the constructor as:
this.state = {
addresses: []
};
And then, you could populate the state
once all the promises resolve:
componentDidMount() {
fetch('.....')
.then(result => result.json())
.then(addresses => {
Promise.all(addresses.map(address => Geocode.fromAddress(address)))
.then(geocodedAddresses => {
const addressesState = addresses.map((address, i) => {
return {
address,
geocodedAddress: geocodedAddresses[i]
};
});
this.setState({ addresses: addressesState })
});
}))
}
}
Upvotes: 1
Reputation: 2018
You're right it's the async is slightly off. The console.log
will populate after it "appears" in the console, but when setState
has been called (and also when console.log
has been called) its value is still []
.
if you're using async/await
you can wait for the fetch
chain to completely finish, or put the setState
within the then
. With the setState
callback, it's probably better to do the latter.
componentDidMount() {
const geocodedAddresses = []
fetch('.....')
.then(result => result.json())
.then(addresses => this.setState({ addresses }, function() {
this.state.addresses.forEach(address => {
Geocode.fromAddress(address).then(geocodedAddress => {
geocodedAddresses.push(geocodedAddress['results'][0]['geometry']['location'])
})
})
console.log(geocodedAddresses) //Correctly contains the geocoded addresses
this.setState({ geocodedAddresses })
}))
}}
or
componentDidMount = async () => {
const geocodedAddresses = []
let addresses = await fetch('.....').then(result => result.json())
addresses.forEach(address => { // assuming this is sync
Geocode.fromAddress(address).then(geocodedAddress => {
geocodedAddresses.push(geocodedAddress['results'][0]['geometry']['location'])
})
})
this.setState({ geocodedAddresses,addresses })
}}
Upvotes: 0