I'm building a weather app, and I'm looking for some advice for best practices for updating state in a child component based on props sent from parent component AFTER an async call from parent component.
I have a parent component that makes an async/await call in the componentDidMount()
method to the navigator.geolocation
and returns latitude and longitude which I want to send to the child component as props. Then, in the child component 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 componentDidMount()
in the child because it mounts before the parent async/await call returns.
The problem is the application flow: The parent component mounts and renders, sending props to child as null
. The child component 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 componentDidMount()
, parent re-renders and sends props to child with correct values as lat
and long
. Child component 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 componentDidUpdate()
without re-rendering into a infinite loop.
So, what's a good way to accomplish this task?
PARENT COMPONENT:
class Container extends React.Component {
state = {
cityState: {
city: "",
state: ""
},
latLong: {
lat: null,
long: null
}
};
componentDidMount() {
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_components[0].long_name,
state: response.data.results[0].address_components[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 componentDidUpdate() {
********** A bunch of logic in here to extract Weather API response
into 5 separate arrays which will each be sent to the <Day /> child components
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
Solution : As I had mentioned earlier — there are no direct ways to mutate a prop directly in child components. However when we pass the props from parent to child, we can also pass the function(with the logic) which changes the props.
To update the parent state from the children component, either we can use additional dependencies like Redux or we can use this simple method of passing the state of the parent to the children and handling it accordingly.
React components use props to communicate with each other. Every parent component can pass some information to its child components by giving them props. Props might remind you of HTML attributes, but you can pass any JavaScript value through them, including objects, arrays, and functions.
You may use the componentWillReceiveProps
lifecycle method.
The first child component render some props like lat
and lng
are null, then you can do something like:
async componentWillReceiveProps(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 componentDidUpdate
as you mentioned you need to run a conditional statement, something like:
componentDidUpdate(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 component and pass it down to the child component.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With