Weather App React 点击三下更新状态和传递 props

Weather App React taking three clicks to update state and pass props

对于我的天气应用程序,目标是让用户输入经纬度并查看八天的预报。它有效,但我必须点击提交三次才能正确获取数据。

目前正在努力确保 API 仅在用户单击提交时调用。不确定我是否需要 onclick 和 onsubmit,但这似乎是 API fetch 不会在用户每次键入内容时调用的唯一方法。

weatherView:用户输入信息并将其作为道具发送的地方。

import React, { Component } from 'react';
import { WeatherCard } from './weatherCard';

export class WeatherView extends Component {
    constructor(props) {
        super(props);
        this.state = {
            lat: "",
            long: "",
            valueLat: "",
            valueLong: "",
            check: false,
            latLongBool: false,
            latLong: ""
        }
        this.onChangeLat = this.onChangeLat.bind(this);
        this.onChangeLong = this.onChangeLong.bind(this);
        this.onSubmit = this.onSubmit.bind(this);
        this.onClick = this.onClick.bind(this);
    }

    onChangeLat = (e) => {
        this.setState({
            valueLat: e.target.value,
            check: false        
        });
    }

    onChangeLong = (e) => {
        this.setState({
            valueLong: e.target.value,
            check: false
        });
    }

    onSubmit = (e) => {
        e.preventDefault()
        if (this.state.valueLat === " " || this.state.valueLong === " ") {
            alert("You must enter something");
        } 
            this.setState({
                lat: this.state.valueLat,
                latLongBool: true,
                long: this.state.valueLong
            })
            console.log("lat and long stored")

    }



     onClick = (e) => {


            this.setState({
                latLong: this.state.lat + "," + this.state.long,
                check: true

            })
            console.log("Prop to send " + this.state.latLong)

    }

    shouldComponentUpdate(nextState) {
        console.log('shouldComponentUpdate activated 1');
    return this.state.latLong !== nextState.latLong;
    }

    render() {
        return(
            <div>
                <h1>Welcome to the Weather App!</h1>
                <form onSubmit={this.onSubmit}>
                    Enter the Latitude in decimal format: <input type="text" name="lat" value={this.state.valueLat} onChange={this.onChangeLat}/> 
                    <br/>
                    Enter the Longitude in decimal format: <input type="text" name="long" value={this.state.valueLong} onChange={this.onChangeLong}/> 
                    <br/>
                    <button onClick={this.onClick} >Submit</button>
                </form>
                <WeatherCard latLong = {this.state.latLong} check={this.state.check}/>
            </div>
        )
    }
}

天气卡:

import React, { Component } from 'react';
import ReactAnimatedWeather from 'react-animated-weather';


const defaults = [
{
    icon: 'CLEAR_DAY',
    color: 'white',
    size: 175,
    animate: true
},
{
    icon: 'CLEAR_NIGHT',
    color: 'white',
    size: 175,
    animate: true
},
{
    icon: 'PARTLY_CLOUDY_DAY',
    color: 'white',
    size: 175,
    animate: true
},
{
    icon: 'PARTLY_CLOUDY_NIGHT',
    color: 'white',
    size: 175,
    animate: true
},
{
    icon: 'CLOUDY',
    color: 'white',
    size: 175,
    animate: true
},
{
    icon: 'RAIN',
    color: 'white',
    size: 175,
    animate: true
},
{
    icon: 'SLEET',
    color: 'white',
    size: 175,
    animate: true
},
{
    icon: 'SNOW',
    color: 'white',
    size: 175,
    animate: true
},
{
    icon: 'WIND',
    color: 'white',
    size: 175,
    animate: true
},
{
    icon: 'FOG',
    color: 'white',
    size: 175,
    animate: true
}
];


function iconConverter(arg){
    switch (arg) {
        case 'clear-day': return 0;
            break;
        case 'clear-night': return 1;
            break;
        case 'partly-cloudy-day': return 2;
            break;
        case 'partly-cloudy-night': return 3;
            break;
        case 'cloudy': return 4;
            break;
        case 'rain': return 5;
            break;
        case 'sleet': return 6;
            break;
        case 'snow': return 7;
            break;
        case 'wind': return 8;
            break;
        case 'fog': return 9;
            break;

    }
}

const WCard = ({day, high, low, humidity, summary, sunrise, sunset, windspeed, time, rainProb, icon}) =>{
    return (
        <div>
            <p>{time}</p>
            <div id='wCardIcon'>

                <ReactAnimatedWeather

                    icon={defaults[iconConverter(icon)].icon}
                    color={defaults[iconConverter(icon)].color}
                    size={defaults[iconConverter(icon)].size}
                    animate={defaults[iconConverter(icon)].animate}
                  />
                <div>
                    <p>&#8679; {high}&#8457;</p>
                    <p>{low}&#8457; &#8681;</p>
                </div>
            </div>
            <p id="wCardSum">{summary}</p>
            <p>Humidity: {humidity}%</p>
            <p>Wind speed: {windspeed}mph</p>
            <p>Sunrise: {sunrise}</p>
            <p>Sunset: {sunset}</p>
            <p>Chance of rain: {rainProb}%</p>

        </div>
    )};



// const weatherAPI = 'https://api.darksky.net/forecast/926bb6de03f1ae8575d48aaeb2fc9b83/34.0522,-118.2437';

const weatherAPI = 'https://api.darksky.net/forecast/926bb6de03f1ae8575d48aaeb2fc9b83/';

export class WeatherCard extends Component {
    constructor(props) {
        super(props)
        this.state = {
            requestFailed: false,
            info: '',
            latLongSubmitted: false,
            latLongValue: this.props.latLong,
            weatherAPI: 'https://api.darksky.net/forecast/926bb6de03f1ae8575d48aaeb2fc9b83/'
        }
    }   



    componentWillReceiveProps(nextProps){
        if (this.props.check) {
        console.log("Receive Props activated")
        console.log("Prop: " + this.props.latLong)
        console.log("Value for API" + this.latLongValue)

        if(this.props.latLong !== nextProps.latLong) {
            this.setState({
                latLongValue: nextProps.latLong,
                latLongSubmitted: true
            })      
            console.log(this.latLongValue)
        }

            console.log('componentDidMount is running')
            fetch(this.state.weatherAPI + this.state.latLongValue)
            .then(response => {
                if (!response.ok) {
                    throw Error("Network request failed")
                }
                return response;
            })
            .then(data => data.json())
            .then(data => {
                this.setState({
                    info: data
                })
                console.log(data)
            }, () => {
                this.setState({
                requestFailed: true
                })
            })
        }
    }

    shouldComponentUpdate(nextProps) {
        console.log('shouldComponentUpdate activated 2');
    return this.state.latLongValue !== nextProps.latLongValue;
    }




    timeDateConverter(tempTime) {
        var time = tempTime *1000;
        var d = new Date(time);
        var formattedDate = (d.getMonth() + 1) + "/" + d.getDate() + "/" + d.getFullYear();

        return formattedDate
    }

    removeMilitary(hours){ 

        if (hours > 0 && hours <= 12) {
            hours = "" + hours;
        } else if (hours > 12) {
            hours = "" + (hours - 12);
        } else if (hours === 0) {
            hours= "12";
        }
        return hours;
    }

    timeConverter(tempTime) {
        var time = tempTime *1000;
        var d = new Date(time);
        var hours = d.getHours();
        if (hours>=12){                 //Adding endings
                var suffix = "P.M.";}
            else{
                suffix = "A.M.";}
        var minutes = (d.getMinutes() < 10) ? "0" + d.getMinutes() : d.getMinutes();

        hours = this.removeMilitary(hours);

        var formattedTime = hours + ":" + minutes + " " + suffix;

        return formattedTime;
    }


    render() {
        if (!this.state.latLongSubmitted) return <p>Waiting for coordinates... try 34.0522, -118.2437</p>
        // if (this.state.requestFailed) return <p>Failed</p>
        if (!this.state.info) return <p>Loading...</p>
        return(
            <div>
                <h1>The current temperature in {this.state.info.timezone} is: {this.state.info.currently.apparentTemperature}&#8457;.</h1>
                <h1>The 8 day forecast for {this.state.info.timezone}:</h1>
                <ul>
                    {this.state.info.daily.data.map((day, id) => 
                        <div key={{id}>{day}} id="weatherCard">
                            <WCard time={this.timeDateConverter(day.time)}
                                high={day.temperatureHigh}
                                low={day.temperatureLow}
                                summary={day.summary}
                                icon={day.icon}
                                humidity={day.humidity}
                                sunrise={this.timeConverter(day.sunriseTime)}
                                sunset={this.timeConverter(day.sunsetTime)}
                                rainProb={day.precipProbability}
                                windspeed={day.windSpeed}
                            />
                        </div>
                    )}
                </ul>

                <a href="https://darksky.net/poweredby/">Powered by DarkSky</a>
            </div>
        )
    }
}

好的..这里有很多要解决的问题,但让我们从问题的核心开始:

在 WeatherCard 里面你有这个:

componentWillReceiveProps(nextProps){
    if (this.props.check)

当然这不会第一次触发,因为你正在检查旧道具。

componentWillReceiveProps(nextProps){
    if (nextProps.check) {

神奇!有效。

好的,但是..让我们解决一些其他问题。

1) 您已经在使用 class 属性 (method = e =>),因此您可以减少很多麻烦:

整个街区:

class WeatherView extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
        lat: "",
        long: "",
        valueLat: "",
        valueLong: "",
        check: false,
        latLongBool: false,
        latLong: ""
    }
    this.onChangeLat = this.onChangeLat.bind(this);
    this.onChangeLong = this.onChangeLong.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
    this.onClick = this.onClick.bind(this);
  }

可以替换为:

class WeatherView extends React.Component {
  state = {
      lat: "",
      long: "",
      valueLat: "",
      valueLong: "",
      check: false,
      latLongBool: false,
      latLong: ""
  }

如果你不使用箭头函数,你只需要绑定东西,这是一回事。

2) 您不需要输入值的重复状态以及这些输入值变成的东西。你可以完全放弃state.valueLongstate.valueLong..你只需要state.latstate.long

考虑到这一点,您可以完全放弃 <form>onSubmit

突发: 我的电脑快没电了。回家后我必须完成这个答案,但我会写更多。

与此同时,您可以在此处看到大部分已修复的版本 https://stackblitz.com/edit/react-hrfmuh?file=WeatherView.js api 调用不起作用,但会像您期望的那样触发

我会从三个方面来解决这个问题。我已经重写了你的一些组件。我删除了不必要的状态道具和代码。您可以复制并粘贴然后尝试分析代码。如果你有任何问题,我在这里

WeatherApp.js

import React, { Component } from 'react';
import { WeatherForm } from './WeatherForm';
import { WeatherCard } from './WeatherCard';

export class WeatherApp extends Component {
    constructor(props) {
      super(props);
        this.state = {
            lat: 0,
            long: 0,
        }
        this.onSubmit = this.onSubmit.bind(this);
    }

    onSubmit = (lat, long) => {
        this.setState({
          lat,
          long
        });
    }


    render() {
        return(
          <div>
            <WeatherForm onSubmit={this.onSubmit}/>
            <WeatherCard lat={this.state.lat} long={this.state.long}/>
          </div>

        )
    }
}

WeatherForm.js

import React, { Component } from 'react';

export class WeatherForm extends Component {

  constructor(props) {
      super(props);
      this.state = {
          lat: 0,
          long: 0,
      }
      this.onChangeLat = this.onChangeLat.bind(this);
      this.onChangeLong = this.onChangeLong.bind(this);
      this.onSubmit = this.onSubmit.bind(this);
  }

  onChangeLat = (e) => {

      let value = e.target.value;
      if(!isNaN(value) ){
        this.setState({
            lat: e.target.value
        });
      }
  }

  onChangeLong = (e) => {
    let value = e.target.value;
    if(!isNaN(value) ){
      this.setState({
          long: e.target.value
      });
    }
  }

  onSubmit = (e) => {
    e.preventDefault();
    if (this.state.lat.length == 0 || this.state.long.length == 0) {
        alert("You must enter something");
    }else{
      this.props.onSubmit(this.state.lat, this.state.long)
    }

  }

  render() {
      return(
          <div>
              <h1>Welcome to the Weather App!</h1>
              <form onSubmit={this.onSubmit}>
                  Enter the Latitude in decimal format: <input type="text" name="lat" value={this.state.lat} onChange={this.onChangeLat}/>
                  <br/>
                  Enter the Longitude in decimal format: <input type="text" name="long" value={this.state.long} onChange={this.onChangeLong}/>
                  <br/>
                  <button >Submit</button>
              </form>
          </div>
      )
  }
}

WeatherCard.js

import React, { Component } from 'react';
import ReactAnimatedWeather from 'react-animated-weather';


const defaults = [
  {
      icon: 'CLEAR_DAY',
      color: 'white',
      size: 175,
      animate: true
  },
  {
      icon: 'CLEAR_NIGHT',
      color: 'white',
      size: 175,
      animate: true
  },
  {
      icon: 'PARTLY_CLOUDY_DAY',
      color: 'white',
      size: 175,
      animate: true
  },
  {
      icon: 'PARTLY_CLOUDY_NIGHT',
      color: 'white',
      size: 175,
      animate: true
  },
  {
      icon: 'CLOUDY',
      color: 'white',
      size: 175,
      animate: true
  },
  {
      icon: 'RAIN',
      color: 'white',
      size: 175,
      animate: true
  },
  {
      icon: 'SLEET',
      color: 'white',
      size: 175,
      animate: true
  },
  {
      icon: 'SNOW',
      color: 'white',
      size: 175,
      animate: true
  },
  {
      icon: 'WIND',
      color: 'white',
      size: 175,
      animate: true
  },
  {
      icon: 'FOG',
      color: 'white',
      size: 175,
      animate: true
  }
];

function iconConverter(arg){
    switch (arg) {
        case 'clear-day': return 0;
            break;
        case 'clear-night': return 1;
            break;
        case 'partly-cloudy-day': return 2;
            break;
        case 'partly-cloudy-night': return 3;
            break;
        case 'cloudy': return 4;
            break;
        case 'rain': return 5;
            break;
        case 'sleet': return 6;
            break;
        case 'snow': return 7;
            break;
        case 'wind': return 8;
            break;
        case 'fog': return 9;
            break;

    }
}

const WCard = ({day, high, low, humidity, summary, sunrise, sunset, windspeed, time, rainProb, icon}) =>{
    return (
        <div>
            <p>{time}</p>
            <div id='wCardIcon'>

                <ReactAnimatedWeather

                    icon={defaults[iconConverter(icon)].icon}
                    color={defaults[iconConverter(icon)].color}
                    size={defaults[iconConverter(icon)].size}
                    animate={defaults[iconConverter(icon)].animate}
                  />
                <div>
                    <p>&#8679; {high}&#8457;</p>
                    <p>{low}&#8457; &#8681;</p>
                </div>
            </div>
            <p id="wCardSum">{summary}</p>
            <p>Humidity: {humidity}%</p>
            <p>Wind speed: {windspeed}mph</p>
            <p>Sunrise: {sunrise}</p>
            <p>Sunset: {sunset}</p>
            <p>Chance of rain: {rainProb}%</p>

        </div>
    )};

export class WeatherCard extends Component {
    constructor(props) {
        super(props)
        this.state = {
            requestFailed: false,
            info: undefined,
            latLongValue: this.props.latLong
        }
    }

    componentDidMount(){
      this.fetchData(this.props.lat, this.props.long);
    }

    componentWillReceiveProps(nextProps){
       this.fetchData(nextProps.lat, nextProps.long);
    }

    timeDateConverter(tempTime) {
        var time = tempTime *1000;
        var d = new Date(time);
        var formattedDate = (d.getMonth() + 1) + "/" + d.getDate() + "/" + d.getFullYear();

        return formattedDate
    }

    removeMilitary(hours){

        if (hours > 0 && hours <= 12) {
            hours = "" + hours;
        } else if (hours > 12) {
            hours = "" + (hours - 12);
        } else if (hours === 0) {
            hours= "12";
        }
        return hours;
    }

    timeConverter(tempTime) {
        var time = tempTime *1000;
        var d = new Date(time);
        var hours = d.getHours();
        if (hours>=12){                 //Adding endings
                var suffix = "P.M.";}
            else{
                suffix = "A.M.";}
        var minutes = (d.getMinutes() < 10) ? "0" + d.getMinutes() : d.getMinutes();

        hours = this.removeMilitary(hours);

        var formattedTime = hours + ":" + minutes + " " + suffix;

        return formattedTime;
    }

    fetchData(lat, long){
      const weatherRequest = `https://api.darksky.net/forecast/fbdca57e2ef5b4ac0f12e3d3779f090e/${lat},${long}`;
      console.log(weatherRequest);
      fetch(weatherRequest).then( data => data.json() ).then( data => {
            this.setState({
                info: data,
                requestFailed: true
            });
        }, () => {
            this.setState({
            requestFailed: true
            })
      })
    }


    render() {
        return(
            this.state.info ? (<div>
                <h1>The current temperature in {this.state.info.timezone} is: {this.state.info.currently.apparentTemperature}</h1>
                <h1>The 8 day forecast for {this.state.info.timezone}:</h1>
                <ul>
                    {this.state.info.daily.data.map((day, id) =>
                        <div key={'_' + Math.random().toString(36).substr(2, 9)} id="weatherCard">
                            <WCard time={this.timeDateConverter(day.time)}
                                high={day.temperatureHigh}
                                low={day.temperatureLow}
                                summary={day.summary}
                                icon={day.icon}
                                humidity={day.humidity}
                                sunrise={this.timeConverter(day.sunriseTime)}
                                sunset={this.timeConverter(day.sunsetTime)}
                                rainProb={day.precipProbability}
                                windspeed={day.windSpeed}
                            />
                        </div>
                    )}
                </ul>

                <a href="https://darksky.net/poweredby/">Powered by DarkSky</a>

            </div>
          ) : <div>Loading</div>
        )
    }
}

将属性 type="submit" 添加到您的按钮

<button type="submit"></button>