简体   繁体   English

从子组件调用时未设置 setState

[英]setState not setting when called from child component

I have a simple app which fetches some weather JSON and displays it.我有一个简单的应用程序,它获取一些天气 JSON 并显示它。 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初始状态在 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.接下来,我有我的主 App 类,然后我有一个包含表单 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).我在那里使用回调函数来设置查询类型(位置或 ID)。 An example of one of the call back functions in App.js is: App.js 中回调函数之一的示例是:

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

This is called in the form JS using:这在表单 JS 中使用:

 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.事实上,在 fetch 中设置的其他变量,如 queryString 直到第二次点击才会设置。

App.js应用程序.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搜索表单.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)在你的App.js构造函数中添加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.该行将this的上下文绑定到当前组件,因此当从子组件调用时,将更新父状态。

try to put your this.getWeatherData() into the componentDidMount and remove it from the constructor尝试将您的 this.getWeatherData() 放入 componentDidMount 并将其从构造函数中删除

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.我认为问题来自这样一个事实,即当您调用getWeatherData ,您不知道setState是否会结束,因为它是一个异步方法。 (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.因此,在不确定组件状态的情况下,确保在调用方法之前完成setState的最佳方法是使用 setState 的 callBack 参数来确保它在setState方法完成后运行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM