【问题标题】:useState variable is called before useEffect API call在 useEffect API 调用之前调用 useState 变量
【发布时间】:2021-10-02 17:28:43
【问题描述】:

据我了解,useEffect 钩子最后作为 sideEffect 运行。我正在尝试控制台日志 data.main.temp。我可以理解它还不知道那是什么,因为它正在从后面运行的 useEffect 钩子中的 api 中获取数据。

在调用 api 之后,我如何能够访问或控制台日志 data.main.temp? (感觉setTimout是作弊方式?)

import React, { useState, useEffect } from "react";
import Button from "../UI/Button";
import styles from "./Weather.module.css";
import moment from "moment";
import Card from "../UI/Card";

export default function Weather() {
  //State Management//
  const [lat, setLat] = useState([]);
  const [long, setLong] = useState([]);
  const [data, setData] = useState([]);

  //openWeather API key
  const key = "xxxxxxxxxxxxxxxxxxxxxxxxxx";

  useEffect(() => {
    const fetchData = async () => {
      //get coordinates//
      navigator.geolocation.getCurrentPosition(function (position) {
        setLat(position.coords.latitude);
        setLong(position.coords.longitude);
      });
      //fetch openWeather api//
      await fetch(`https://api.openweathermap.org/data/2.5/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${key}`)
        .then((res) => res.json())
        .then((result) => {
          setData(result);
          console.log(result);
        });
    };
    fetchData();
  }, [lat, long]);


//Examples of what I want, they run too early before api//
console.log(data.main.temp);
const Farenheit = data.main.temp * 1.8 + 32;

  return (
    <Card>
      {typeof data.main != "undefined" ? (
        <div className={`${styles.weatherContainer} ${styles.clouds}`}>
          <h2>Weather</h2>
          <p>{data.name}</p>
          <p>{data.main.temp * 1.8 + 32} &deg;F</p>
          <p>{data.weather[0].description}</p>
          <hr></hr>
          <h2>Date</h2>
          <p>{moment().format("dddd")}</p>
          <p>{moment().format("LL")}</p>
        </div>
      ) : (
        <div></div>
      )}
    </Card>
  );
}

【问题讨论】:

    标签: reactjs api fetch use-effect use-state


    【解决方案1】:

    你是对的,效果函数在第一次渲染之后运行,这意味着你需要以某种方式等待,直到你的 api 调用完成。这样做的一种常见方法是引入另一个状态标志,指示数据是否可用。

    另一件不遵循 react 良好实践的事情是,你的效果函数不止做一件事。

    我还添加了简单的错误处理并清理了混合的承诺和异步等待

    这是你的重构代码

    import React, { useState, useEffect } from "react";
    import Button from "../UI/Button";
    import styles from "./Weather.module.css";
    import moment from "moment";
    import Card from "../UI/Card";
    
    //openWeather API key
    const key = "xxxxxxxxxxxxxxxxxxxxxxxxxx";
    
    export default function Weather() {
      //State Management//
      const [lat, setLat] = useState();
      const [long, setLong] = useState();
      const [data, setData] = useState();
      const [error, setError] = useState();
      const [loading, setLoading] = useState(false);
    
      useEffect(() => {
        navigator.geolocation.getCurrentPosition((position) => {
          setLat(position.coords.latitude);
          setLong(position.coords.longitude);
        });
      }, []);
    
      useEffect(() => {
        const fetchData = async () => {
          if (lat && long && key) {
            try {
              setLoading(true);
              const response = await fetch(
                `https://api.openweathermap.org/data/2.5/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${key}`
              );
              const data = await response.json();
              setData(data);
              setLoading(false);
            } catch (err) {
              setError(err);
              setLoading(false);
            }
          }
        };
        fetchData();
      }, [lat, long]);
    
      if (error) {
        return <div>some error occurred...</div>;
      }
    
      return (
        <Card>
          {loading || !data ? (
            <div>loading...</div>
          ) : (
            <div className={`${styles.weatherContainer} ${styles.clouds}`}>
              <h2>Weather</h2>
              <p>{data.name}</p>
              <p>{data.main.temp * 1.8 + 32} &deg;F</p>
              <p>{data.weather[0].description}</p>
              <hr></hr>
              <h2>Date</h2>
              <p>{moment().format("dddd")}</p>
              <p>{moment().format("LL")}</p>
            </div>
          )}
        </Card>
      );
    }
    
    
    

    【讨论】:

    • 我明白了,非常彻底。我很感激花时间重构代码,我不知道如何使用单独的 useEffects!
    • 有一个问题,如果我是对的,第一个 useEffect 只会运行一次,而且由于用户的 lcoation 可能会改变,我不需要一个数组依赖项吗?
    • {Math.round(parseFloat(data.main.temp) * 1.8 + 32)} °F

      顺便说一句,如果有人需要在这里转换为 Farenhiet去
    • 是的,空的依赖数组意味着效果只会运行一次(挂载时)。 getCurrentPosition API 是一种“一次性”操作,因此将其放入“onMount”挂钩是有意义的。但是,如果您想查看用户位置,则需要在钩子内使用navigator.geolocation.watchPosition API。这个 API 让你订阅位置变化。但需要注意的重要一点是,您必须使用效果清理(从效果返回的函数)才能取消注册处理程序return () =&gt; navigator.geolocation.clearWatch(id);
    【解决方案2】:

    你可以使用另一个useEffect,这取决于改变数据状态

    useEfect(() => {
      if (data) {
        // do something with data
      }
    }, [data])
    

    【讨论】:

      【解决方案3】:

      您可以创建一个简单的函数并在您的 API 调用响应中调用它,并直接从 api 响应中传入 data,这样您就可以在有响应时立即访问数据。

      例如

      ...
      .then((result) => {
        setData(result);
        getDataValue(result) // this function will be called when the response comes in and you can use the value for anything
        console.log(result);
      });
      

      方法2:

      您可以使用 useEffect 挂钩来监控数据状态的变化,这样只要该状态有更新,您就可以使用该值来做任何您想做的事情。这是我不太喜欢的选项。

      useEffect(() => {
         //this hook will run whenever data changes, the initial value of data will however be what the initial value of the state is
      
      console.log(data) //initial value = [] , next value => response from API
      },[data])
      

      【讨论】:

      • 谢谢!我确实喜欢选项一的概念是的
      猜你喜欢
      • 2021-01-12
      • 2020-03-19
      • 1970-01-01
      • 2021-06-08
      • 1970-01-01
      • 1970-01-01
      • 2020-09-24
      • 2021-02-06
      相关资源
      最近更新 更多