从子组件调用时未设置setState

时间:2019-04-03 14:53:24

标签: javascript reactjs

我有一个简单的应用程序,该应用程序获取一些天气JSON并将其显示。用户可以输入位置,也可以单击“获取幸运”按钮,以获取随机城市。初始状态在App.js中设置

    this.state = {
      error: '',
      status: '',
      queryString: 'london,gb',
      queryID: '',
      queryType: 'q',
      cityData: cityData, 
      weatherData: {},
      isLoaded: false
    }

接下来,我有主App类,然后有一个名为gubbins的子组件。我在应用程序渲染中将其称为:

      <SearchForm
        queryString={this.state.queryString}
        handleChange={this.handleChange}
        setQueryType={this.setQueryType}
        setQueryID={this.setQueryID}
        getWeatherData={this.getWeatherData}
      />

我在那里使用回调函数来设置查询类型(位置或ID)。 App.js中的回调函数之一的示例是:

  setQueryType = (queryType) => {
    this.setState({
      queryType: queryType
    })
  }

以JS形式使用:

 props.setQueryType(e.target.attributes.query.value)

现在,这是问题的症结所在:状态第一次不会更新,但是第二次点击会吗?实际上,在第二次点击之前不会设置其他在提取中设置的查询变量,例如queryString。

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

3 个答案:

答案 0 :(得分:1)

在您的App.js构造函数中,添加this.setQueryType = this.setQueryType.bind(this)

该行会将this的上下文绑定到当前组件,因此当从子级调用时,将更新父级状态。

答案 1 :(得分:0)

尝试将this.getWeatherData()放入componentDidMount并将其从构造函数中删除

componentDidMount() {
   this.getWeatherData()
}

答案 2 :(得分:0)

我认为问题出在您致电getWeatherData时, 您不知道setState是否会结束,因为它是一个异步方法。 (如您在documentation中看到的那样)

因此,最好的方法是使用setState的callBack参数以确保它在{之后运行,以确保在调用方法之前完成setState而又不确定组件的状态。 {1}}方法已完成。