I’m really new to react and I’m trying to build a simple weather app to improve my skills.
This app should get the user’s location and call an api to get the weather for that location.
To handle the api calls I created a hook that receives the query parameters and calls the api , but I came into a problem.
I have to UseEffects inside the hook (one to get the location and one to get the weather data as soon as I have a position (that position can either come in the query parameters that I pass to the hook , or if they are undefined it should fetch the user’s location).
But my problem is that if I add the query variable to the hook dependencies (to the one that calls the weather API) it runs indefinitely.
Can someone help me ?
Here are the files that I’m using:
Home.js
import { useState } from "react";
import Loader from "./Loader";
import useGetWeather from "./useGetWeather";
import WeatherIcon from "./WeatherIcon";
const Home = () => {
const [latitude, setLatitude] = useState();
const [longitude, setLongitude] = useState();
const query = {
current_weather: true,
hourly: "temperature_2m,weathercode,is_day",
forecast_days: 1,
latitude: latitude,
longitude: longitude,
};
const { locationInfo, weatherData, isLoading, error } = useGetWeather(query);
let countId = 0;
function renderWeatherInfo() {
return (
<div>
<h1>Weather</h1>
<h2>{locationInfo}</h2>
<div className="display-flex vertical-align">
<WeatherIcon
iconId={weatherData.current_weather.weathercode}
isDay={!!weatherData.current_weather.is_day}
/>
<h3>
Temperatura actual:
<span>{weatherData.current_weather.temperature}ºC</span>
</h3>
</div>
<h3>Previsão diária</h3>
<div className="tempWrapper">
{weatherData &&
weatherData.hourly.temperature_2m.flatMap((temp, index) => {
countId++;
let d = new Date(weatherData.hourly.time[index]);
let currDate = new Date();
currDate.setMinutes(0);
currDate.setSeconds(0);
if (d.getHours() < currDate.getHours()) {
return [];
}
return (
<div className="tempItem" key={countId}>
<WeatherIcon
iconId={weatherData.hourly.weathercode[index]}
isDay={!!weatherData.hourly.is_day[index]}
showLabel={true}
/>
<p>
{temp} {weatherData.hourly_units.temperature_2m}
</p>
<p>
{d.getHours() === currDate.getHours()
? "Now"
: d.getHours()}
</p>
</div>
);
})}
</div>
</div>
);
}
return (
<div>
{isLoading && <Loader />}
{!isLoading && !error && renderWeatherInfo()}
</div>
);
};
export default Home;
useGetWeather.js
import { useState, useEffect } from "react";
const useGetWeather = (query) => {
const [pos, setPos] = useState({});
const [weatherData, setWeatherData] = useState();
const [locationInfo, setLocationInfo] = useState();
const [isLoadingWeatherData, setIsLoadingWeatherData] = useState(true);
const [isLoadingLocationInfo, setIsLoadingLocationInfo] = useState(false);
const [error, setError] = useState();
useEffect(() => {
if (query.longitude === undefined && query.latitude === undefined) {
navigator.geolocation.getCurrentPosition(function (position) {
setPos(position.coords);
});
} else {
setPos({ latitude: query.latitude, longitude: query.longitude });
}
}, [query.latitude, query.longitude]);
useEffect(() => {
const abortCont = new AbortController();
let localQuery = query;
localQuery.latitude = pos.latitude;
localQuery.longitude = pos.longitude;
if (typeof pos.longitude !== "undefined") {
fetch(
"https://api.open-meteo.com/v1/forecast?" +
new URLSearchParams(localQuery).toString(),
{ signal: abortCont.signal }
)
.then((res) => {
if (!res.ok) {
throw Error("Could not fetch the data for that resource");
}
return res.json();
})
.then((data) => {
setWeatherData(data);
setIsLoadingWeatherData(false);
setError(null);
})
.catch((err) => {
setError(err.message);
setIsLoadingWeatherData(false);
console.log(err.message);
});
fetch(
"https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=" +
pos.latitude +
"&longitude=" +
pos.longitude +
"&localityLanguage=en",
{ signal: abortCont.signal }
)
.then((res) => {
if (!res.ok) {
throw Error("Could not fetch the data for that resource");
}
return res.json();
})
.then((data) => {
setLocationInfo(
data.city + "," + data.principalSubdivision + "," + data.countryName
);
setIsLoadingLocationInfo(false);
setError(null);
})
.catch((err) => {
setError(err.message);
console.log(err.message);
setIsLoadingLocationInfo(false);
});
}
return () => abortCont.abort();
}, [pos, query]);
return {
locationInfo,
weatherData,
isLoading: isLoadingWeatherData || isLoadingLocationInfo,
error,
};
};
export default useGetWeather;
2
Answers
its running indefinitely because the dependencies pos and query are objects and every time an object is recreated or reassigned it is considered new even if the content remains the same. so every time your hook re-runs it is seeing pos and query as new objects, hence the infinite loop.
i suggest you divide the ‘useEffect’ into two, one to handle the change in position and another to handle changes in query. Also store latitude and longitude individually instead of storing the entire pos object in state.
use sth like this:
this makes sure that the fetch operation is only triggered if latitude or longitude changes. it also simplifies the dependencies.
This happens because query is an object. Since you do not memoize it, it gets created on each render, which then gets grabbed in the hooks dependency array, resulting in infinite renders. Try using
useMemo
hook which comes with react.