OK, so I was able to generate some dummy data on the API web site.
So, with your App.handleClick, I think the problem was that you had two setState calls trying to manipulate the same section of state. The [docs] actually recommend that when your new state is dependent on your old state (like here) or if there are potential race conditions, you should use the functional version of setState. Here would be an implementation for currentWeatherData:
fetchCurrentWeather(search_query)
.then(currentWeatherData => {
this.setState(oldState => ({
data: {...oldState.data, currentWeatherData}
}));
})
Or more expanded for readability (I’m not sure how comfortable you are with ES6 shorthands):
fetchCurrentWeather(search_query)
.then(currentWeatherData => {
this.setState(oldState => {
const newState = { data: {...oldState.data, currentWeatherData} };
return newState;
});
});
Using this functional form of setState will prevent a race condition - at least it’s working for me now.
A few other thoughts as a scan your code…
This is what you have now:
fetchCurrentWeather(search_query)
.then((currentWeatherData) => {
this.setState({
data: {...this.state.data, currentWeatherData}
})
console.log(this.state.data.currentWeatherData);
console.log(this.state.loading);
})
fetchForecast(search_query)
.then((forecastData) => {
this.setState({
data: Object.assign(this.state.data, {...this.state.data, forecastData})
})
console.log(this.state.data.forecastData);
console.log(this.state.loading);
})
this.setState({loading: false,});
There are a few things here that I don’t think are doing what you think they are doing. For example, those console statements on lines 6 and 7? They are going to run before the preceding setState is done. If you want to see what they look like after the setState is run, you can provide a callback as a second parameter after setState:
fetchCurrentWeather(search_query)
.then(
(currentWeatherData) => {
this.setState({
data: {...this.state.data, currentWeatherData}
},
() => {
console.log(this.state.data.currentWeatherData);
console.log(this.state.loading);
}
)
And that last line, that is not going to wait for those fetch calls, but is going to run instantly. Remember that fetch is async. (Yes, I know it’s hard to learn to think this way, but it is essential to learning JS.) That should be inside the callback to the fetch calls, it could even be in the setState there that assigns the data.
But that brings up another problem with your loading flag - you have two different async processes. One solution would be to have two flags. Another solution would be instead of a flag to have a count of current fetch processes. You should have a state var like activeFetches. It starts at 0, and gets incremented when a fetch starts and gets decremented when one ends. If it is > 0, you know you are still waiting on something.
Some other nitpicky stuff (if this were at work and I was reviewing your code in a PR)…
Your component Current_Weather.js should be CurrentWeather.js by convention. Also, “search_data” should be searchData.
I would move your state out of App.js and into CurrentWeather.js. As you build up app sizes, App.js is going to get clogged up with redux stuiff, navigation/routing stuff, etc. State should be kept in the first common ancestor of whomever needs it. And remember that every component can have state. Not that you need that here, but that messed me up for a while when I was learning.
I think handleClick and handleChange are kind of a vague names for methods, especially in a component as busy as this. But others may disagree. But someone that expands that component later may get lazy and not give them more meaningful names.
Are you familiar with functional components (sometimes also called stateless functional components or SFCs)? They are a little more lightweight than class components. I think it’s a good idea to use SFCs anywhere that you don’t need state or lifecycle methods, which (as your app is now) is everywhere but App.js. The docs. There are even ways to avoid class components when you do need those things now, but baby steps.
I think that if(data.temp !== undefined) {
looks a little clunky. I would have done if(data.temp) {
, checking if it’s truthy. Of course, there is the odd chance that the temp is 0 Kelvin, but I think we can risk it.
And personally, rather than nesting all that stuff up one level and have your return null;
at the end, I’d rather do it at the beginning: if (!data.temp) return null;
Then your other return can follow without having to be nested. Unnecessary nesting makes code harder to read.
I would also recommend using a linter, like ESLint with the Air B&B settings (probably the default). They drive you crazy for a while as they nag about spaces and formatting, but you end up writing in a much more uniform style, and that is almost an industry standard now - it will make your life so much easier if you get a job,
But don’t get me wrong, there is some good stuff here. You’re definitely on your way.