I'm building a weather app, and I'm looking for some advice for best practices for updating state in a child ponent based on props sent from parent ponent AFTER an async call from parent ponent.
I have a parent ponent that makes an async/await call in the ponentDidMount()
method to the navigator.geolocation
and returns latitude and longitude which I want to send to the child ponent as props. Then, in the child ponent I need do an async/await call to the OpenWeatherMap API using the lat and long from the props. I then need to setState()
using the response
. I can't use ponentDidMount()
in the child because it mounts before the parent async/await call returns.
The problem is the application flow: The parent ponent mounts and renders, sending props to child as null
. The child ponent mounts and renders with null
props. Then, the async/await returns a response in parent, sets lat
and long
from response to state in the ponentDidMount()
, parent re-renders and sends props to child with correct values as lat
and long
. Child ponent updates with correct values in props. Now, at this point I need to setState()
with those props, however I obviously can't do it in ponentDidUpdate()
without re-rendering into a infinite loop.
So, what's a good way to acplish this task?
PARENT COMPONENT:
class Container extends React.Component {
state = {
cityState: {
city: "",
state: ""
},
latLong: {
lat: null,
long: null
}
};
ponentDidMount() {
this.getCityAndState();
}
getCityAndState() {
let latlng = "";
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(async position => {
latlng = `${position.coords.latitude},${position.coords.longitude}`;
const response = await googleGeolocation.get(
`json?latlng=${latlng}&location_type=APPROXIMATE&result_type=locality&key=${APIkey}`
);
this.setState({
cityState: {
city: response.data.results[0].address_ponents[0].long_name,
state: response.data.results[0].address_ponents[2].short_name
},
latLong: {
lat: position.coords.latitude,
long: position.coords.longitude
}
});
});
}
}
render() {
return (
<div className="container">
<LocationTime location={this.state.cityState} />
<Forecast location={this.state.latLong} />
</div>
);
}
}
CHILD COMPONENT:
class Forecast extends React.Component {
state = {
today: {},
secondDay: {},
thirdDay: {},
fourthDay: {},
fifthDay: {}
};
async ponentDidUpdate() {
********** A bunch of logic in here to extract Weather API response
into 5 separate arrays which will each be sent to the <Day /> child ponents
after setting the state to those arrays(which doesn't work in this
life-cycle method, currently) **********
}
render() {
return (
<div className="forecast">
<Day forecast={this.state.today} />
<Day forecast={this.state.secondDay} />
<Day forecast={this.state.thirdDay} />
<Day forecast={this.state.fourthDay} />
<Day forecast={this.state.fifthDay} />
</div>
);
}
}
P.S. I always ask convoluted questions that end with fairly simple answers, lol
I'm building a weather app, and I'm looking for some advice for best practices for updating state in a child ponent based on props sent from parent ponent AFTER an async call from parent ponent.
I have a parent ponent that makes an async/await call in the ponentDidMount()
method to the navigator.geolocation
and returns latitude and longitude which I want to send to the child ponent as props. Then, in the child ponent I need do an async/await call to the OpenWeatherMap API using the lat and long from the props. I then need to setState()
using the response
. I can't use ponentDidMount()
in the child because it mounts before the parent async/await call returns.
The problem is the application flow: The parent ponent mounts and renders, sending props to child as null
. The child ponent mounts and renders with null
props. Then, the async/await returns a response in parent, sets lat
and long
from response to state in the ponentDidMount()
, parent re-renders and sends props to child with correct values as lat
and long
. Child ponent updates with correct values in props. Now, at this point I need to setState()
with those props, however I obviously can't do it in ponentDidUpdate()
without re-rendering into a infinite loop.
So, what's a good way to acplish this task?
PARENT COMPONENT:
class Container extends React.Component {
state = {
cityState: {
city: "",
state: ""
},
latLong: {
lat: null,
long: null
}
};
ponentDidMount() {
this.getCityAndState();
}
getCityAndState() {
let latlng = "";
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(async position => {
latlng = `${position.coords.latitude},${position.coords.longitude}`;
const response = await googleGeolocation.get(
`json?latlng=${latlng}&location_type=APPROXIMATE&result_type=locality&key=${APIkey}`
);
this.setState({
cityState: {
city: response.data.results[0].address_ponents[0].long_name,
state: response.data.results[0].address_ponents[2].short_name
},
latLong: {
lat: position.coords.latitude,
long: position.coords.longitude
}
});
});
}
}
render() {
return (
<div className="container">
<LocationTime location={this.state.cityState} />
<Forecast location={this.state.latLong} />
</div>
);
}
}
CHILD COMPONENT:
class Forecast extends React.Component {
state = {
today: {},
secondDay: {},
thirdDay: {},
fourthDay: {},
fifthDay: {}
};
async ponentDidUpdate() {
********** A bunch of logic in here to extract Weather API response
into 5 separate arrays which will each be sent to the <Day /> child ponents
after setting the state to those arrays(which doesn't work in this
life-cycle method, currently) **********
}
render() {
return (
<div className="forecast">
<Day forecast={this.state.today} />
<Day forecast={this.state.secondDay} />
<Day forecast={this.state.thirdDay} />
<Day forecast={this.state.fourthDay} />
<Day forecast={this.state.fifthDay} />
</div>
);
}
}
P.S. I always ask convoluted questions that end with fairly simple answers, lol
Share Improve this question asked Feb 2, 2019 at 23:18 rchaprchap 1111 silver badge9 bronze badges 2- 0xc14m1z's answer is good, but in general it's considered bad React practice to do async calls and then put the response in to ponent state - this is why most apps use a separate global state (e.g. redux). See stackoverflow./a/45972353/5009210 – Duncan Thacker Commented Feb 2, 2019 at 23:43
- Yeah, I was trying to avoid building-in Redux, to try to keep it simpler, but I guess I should refactor it in. – rchap Commented Feb 3, 2019 at 3:15
2 Answers
Reset to default 8You may use the ponentWillReceiveProps
lifecycle method.
The first child ponent render some props like lat
and lng
are null, then you can do something like:
async ponentWillReceiveProps(nextProps) {
if ( this.props.lat !== nextProps.lat || this.props.lng !== nextProps.lng ) {
const response = await YourAPICall(nextProps.lat, nextProps.lng)
this.setState(/* set your things here */)
}
}
Obviously this is just an outline...
Not sure why you use async/await instead of a normal fetch/axios call. In order to prevent entering in an infinite loop in your ponentDidUpdate
as you mentioned you need to run a conditional statement, something like:
ponentDidUpdate(prevState){
if (this.props.propertyYouWantToCheck !== prevState.propertyYouWantToCheck){
// set your state/logic here
}
}
Also you might want to consider to use fetch data only in the parent ponent and pass it down to the child ponent.