lache
lache

Reputation: 830

useState variable is called before useEffect API call

From what I understand useEffect hook runs last as a sideEffect. I am attempting to console log data.main.temp. I can understand that it doesn't know what that is yet, because it is fetching the data from the API in the useEffect hook which runs after.

How would I be able to access or console log data.main.temp AFTER the API call? (I feel like setTimout is the cheating way?)

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>
  );
}

Upvotes: 2

Views: 696

Answers (3)

r4mka
r4mka

Reputation: 246

You're right, the effect function is run after the first render which means you need to wait somehow until your api call is done. One common way to do so is to introduce another state flag which indicate whether the data is available or not.

Another thing which does not follow react good practices is the fact that you're effect function does more than one thing.

I also added trivial error handling and cleaned up mixed promises and async await

here is your refactored code

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>
  );
}


Upvotes: 6

unhackit
unhackit

Reputation: 561

You can create a simple function and call it in your API call response and pass in the data directly from the api response, that way you will have access to the data immediately there's a response.

E.g

...
.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);
});

METHOD 2:

You can use a useEffect hook to monitor changes in the data state, so that whenever there's an update on that state, you can use the value to do whatever you want. This is my less preferred option.

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])

Upvotes: 0

Dmitriy Zhiganov
Dmitriy Zhiganov

Reputation: 1118

You can use another useEffect, which depends on changing the data state

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

Upvotes: 0

Related Questions