I have a simple app which fetches some weather JSON and displays it. The user can either enter a location or they can hit a "Get lucky" button, which fetches a random city. the initial state is set in App.js
this.state = {
error: '',
status: '',
queryString: 'london,gb',
queryID: '',
queryType: 'q',
cityData: cityData,
weatherData: {},
isLoaded: false
}
Next, I have my main App class, then I have a child component called that contains the form gubbins. I call it in app render as follows:
<SearchForm
queryString={this.state.queryString}
handleChange={this.handleChange}
setQueryType={this.setQueryType}
setQueryID={this.setQueryID}
getWeatherData={this.getWeatherData}
/>
I use callback functions in there to set the query type (location or ID). An example of one of the call back functions in App.js is:
setQueryType = (queryType) => {
this.setState({
queryType: queryType
})
}
This is called in the form JS using:
props.setQueryType(e.target.attributes.query.value)
Now, here is the crux of the issue: the state doesn't update the first time, but DOES on the second click? In fact, other vars like queryString set in the fetch are not set until the second click.
App.js
import React, { Component } from 'react';
import './css/App.css';
import WeatherCard from './components/WeatherCard'
import Header from './components/Header'
import SearchForm from './components/SearchForm'
import cityData from './json/city.list'
const config = {
API: 'https://api.openweathermap.org/data/2.5/forecast',
API_KEY: process.env.REACT_APP_OPEN_WEATHER_MAP_API_KEY
}
class App extends Component {
constructor() {
super()
this.state = {
error: '',
status: '',
queryString: 'london,gb',
queryID: '',
queryType: 'q',
cityData: cityData,
weatherData: {},
isLoaded: false
}
this.getWeatherData()
}
getWeatherData = (searchValue="london,gb") => {
let URL
URL = config.API + '?' + this.state.queryType + '='
URL += this.state.queryType === 'q' ? searchValue : this.state.queryID
URL += '&units=metric&APPID=' + config.API_KEY
console.log(URL)
fetch(URL)
.then( result => result.json() )
.then (
(result) => {
if ( result.cod === '200') {
this.setState({
status: result.cod,
weatherData: result,
queryString: result.city.name,
isLoaded: true
})
} else {
this.setState({
status: result.cod,
error: result.message,
isLoaded: false
})
}
},
(error) => {
this.setState({
isLoaded: false,
error: error
})
}
)
console.log(this.state.queryString)
}
handleChange = (event) => {
const { name, value } = event.target
this.setState({
[name]: value
})
}
getWeatherCards = () => {
let cards = []
for (let i = 0; i < this.state.weatherData.cnt; i++) {
cards.push(
<WeatherCard
key={i}
weatherList={this.state.weatherData.list[i]}
/>
)
}
return cards
}
setQueryType = (queryType) => {
this.setState({
queryType: queryType
})
}
setQueryID = () => {
let randomID = Math.floor(Math.random() * this.state.cityData.length)
let randomCityID = this.state.cityData[randomID].id
this.setState({
queryID: randomCityID
})
}
getlocationForm = () => {
return(
<SearchForm
queryString={this.state.queryString}
handleChange={this.handleChange}
setQueryType={this.setQueryType}
setQueryID={this.setQueryID}
getWeatherData={this.getWeatherData}
/>
)
}
render = () => {
if (this.state.status !== '200') {
return (
<div className='App'>
<Header
status={this.state.status}
error={this.state.error}
/>
{this.getlocationForm()}
</div>
)
} else {
return (
<div className='App'>
{
this.state.isLoaded && (
<Header
cityName={this.state.weatherData.city.name}
countryName={this.state.weatherData.city.country}
status={this.state.status}
error={this.state.error}
/>
)
}
{this.getlocationForm()}
{
this.state.isLoaded && (
<div className='weather-cards'>
{this.getWeatherCards()}
</div>
)
}
</div>
)
}
}
}
export default App;
SearchForm.js
import React from 'react'
const SearchForm = (props) => {
let handleChange = function(e) {
props.handleChange(e)
}
let handleClick = function(e) {
e.preventDefault()
props.setQueryType(e.target.attributes.query.value)
if (e.target.attributes.query.value === 'id') {
props.setQueryID()
}
props.getWeatherData()
}
return (
<div>
<form className="search-form">
<input
type="text"
id="query"
name="query"
placeholder="Enter a location..."
onChange={handleChange}
/>
<button
type="submit"
query="q"
onClick={handleClick}
>
Submit
</button>
<button
type="submit"
query="id"
onClick={handleClick}
>
I'm feeling lucky...
</button>
</form>
</div>
)
}
export default SearchForm
In your App.js
constructor add this.setQueryType = this.setQueryType.bind(this)
That line will bind the context of this
to the current component, so when called from a child, will update parent state.
try to put your this.getWeatherData() into the componentDidMount and remove it from the constructor
componentDidMount() {
this.getWeatherData()
}
I think the problem comes from the fact that when you call getWeatherData
, you don't know if the setState
will be over as it is an asynchronous method. (as you can see in the documentation )
So the best way, to ensure that the setState
is done before calling your method without being certain of the state of your component, would be to use the callBack parameter of the setState to ensure it runs after the setState
method has been finished.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.