React, Get Weather Aync Await Errors - reactjs

Still teaching myself React, and was working on a project when I ran into an issue.
I have the below code.
const Weather = (props) => {
const [weather, setWeather] = useState([]);
const [lat, setLat] = useState([]);
const [long, setLong] = useState([]);
useEffect(() => {
const fetchData = async () => {
navigator.geolocation.getCurrentPosition(function (position) {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
await fetch(`https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${long}&appid=SomeID&units=imperial&exclude=minutely`
)
.then((res) => res.json())
.then((result) => {
setWeather(result);
console.log(result);
});
};
fetchData();
}, [lat, long]);
return (
<React.Fragment>
<div className={styles.weather}>
<div className={styles.weather__current}>{weather.current.temp}</div>
<div className={styles.weather__hourly}>Hourly</div>
<div className={styles.weather__daily}>5 Day</div>
</div>
</React.Fragment>
);
};
export default Weather;
My issue is that my request goes out twice before it has the lat and long "populated", on the third attempt it goes out just fine. So I get 2 responses of bad request, with the third being what I want.
My understanding is that in an Async function, the await, will "pause" until all previous variables are "satisfied". I may be misunderstanding that.
I can "hard code" the values, and everything is fine.
Any help would be appreciated.

The issue is that you are setting two of the useEffect's dependency inside the useEffect function itself (lat and long). You need to separate your useEffect into two separate ones: the first to fetch lat and long, the second to fetch the weather.
useEffect(() => {
navigator.geolocation.getCurrentPosition(function (position) {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
}, []);
useEffect(() => {
fetch(`https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${long}&appid=SomeID&units=imperial&exclude=minutely`)
.then((res) => res.json())
.then((result) => {
setWeather(result);
console.log(result);
});
}, [lat, long]);
Alternatively, since you are not rendering lat and long, you could just make them normal variables, not state variables:
useEffect(() => {
navigator.geolocation.getCurrentPosition(function (position) {
const lat = position.coords.latitude);
const long = position.coords.longitude);
fetch(`https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${long}&appid=SomeID&units=imperial&exclude=minutely`)
.then((res) => res.json())
.then((result) => {
setWeather(result);
console.log(result);
});
});
}, []);

Your request is going twice because you are setting lat and long states before setting the weather and as you know everytime you set/change a state in a component, react updates it again.
The solution is to use useRef hook instead of useState for lat and long. You can also use const variable to store temporarily if not using these values anywhere else.

Related

useEffect for chained fetch calls

I'm chaining two fetch calls to retrieve data. The first call gets a token and then second call uses that token to get the data. Here's an example:
fetch("[GET THE TOKEN]")
.then((response) => response.json())
.then(token => {
fetch("[GET DATA]?token="+token)
.then((response) => response.json())
.then((data) => {
return data;
});
})
The issue is that I need to make lots of different calls sometimes within the same component and writing that chained call over and over again can get tedious and if I need to make changes it's a lot of code to edit.
I came up with a functional solution but I haven't stress tested it yet. I'm still a react noob so feedback would be helpful
context.jsx
const [token,setToken] = useState('')
const fetchToken = async () => {
fetch("[GET THE TOKEN]")
.then(response => response.json())
.then(data => {
setToken(data);
});
}
component.jsx
const {token, fetchToken } = useContext(context)
//fetch data function
const [initFetch,setInitFetch] = useState(false);
const fetchData = () => {
fetch("[GET DATA]?token="+token)
.then((response) => response.json())
.then((data) => {
return data;
});
}
//action that inits fetch data
const submitForm = () => {
setInitFetch(true)
}
//useEffect that will trigger the function on initFetch and token const changing values
useEffect(() => {
if (token && initFetch === true) {
fetchData();
}
},[token,initFetch]);
I see that you want to call this fetch function when you submit the form, Therefore, you should not do that with an useEffect, simply because you don't need to.
Use the useCallback hook and create an async function inside of it. Call it almost wherever you want.
See:
const MyComponent = () => {
const {token, fetchToken } = useContext(context)
const [initFetch, setInitFetch] = useState(false);
const fetchData = useCallback(async () => {
const response1 = await fetch(`[GET DATA]?token=${token}`);
const jsonFromResponse1 = await response1.json();
return jsonFromResponse1;
}, [token])
const randomFunc = async () => {
const data = await fetchData()
}
return (
<button onClick={fetchData}>Button</button>
)
}
The dependency array of useCallback is crucial, if you don't insert it, when the token changes, the hook will never update the function with its new value.
You can continue to use then. But I strongly recommend you to try await/async. It will make your code way easier and readable.
What I get from you question, that you are looking for some way to not repeat your fetch calls.
If that's so, I believe you can go with a custom hook that you can call every time you need.
something like that
const useFetchFn=(arg)=>{
fetch(arg)
.then((response) => response.json())
.then((data) => {
return data;
});
}
Then in your component
const [token,setToken] = useState('')
const fetchToken =useFetchFn("[GET THE TOKEN]")

useEffect doesn't run after rendering

I'm kind of confused about how useEffect is triggered and how it work. I wrote a function like this but the useEffect doesn't run at all. I want to fetch the data from the API and then render a page based on the data. But it doesn't trigger the useEffect. If I don't use the useEffect, it will render the page three times.
async function getData() {
var tmpArrData = [];
await fetch("this API is hidden due to the privacy of the company - sorry")
.then((res) => res.json())
.then((data) => {
console.log("data", data);
tmpArrData = data;
});
console.log("tmpData ", tmpArrData);
return tmpArrData;
}
function App() {
const [arrData, setArrData] = useState();
const [loadData, setLoadData] = useState(false);
useEffect(() => {
console.log("if it works, this line should be shown");
const tmpArrData = getData();
setArrData(tmpArrData);
}, [arrData]);
const data = arrData[0];
console.log(data);
return (
<GifCompoment
id = {data.id}
name = {data.name}
activeTimeTo = {data.activeTimeTo}
activeTimeFrom = {data.activeTimeFrom}
requiredPoints = {data.requiredPoints}
imageUrl = {data.imageUrl}
/>
);
}
export default App;
The useEffect hook is guaranteed to run at least once at the end of the initial render.
getData is an async function and the useEffect callback code is not waiting for it to resolve. Easy solution is to chain from the implicitly returned Promise from getData and access the resolved value to update the arrData state. Make sure to remove the state from the useEffect's dependency array so that you don't create a render loop.
The getData implementation could be clean/tightened up by just returning the fetch result, no need to save into a temp variable first.
async function getData() {
return await fetch(".....")
.then((res) => res.json());
}
useEffect(() => {
console.log("if it works, this line should be shown");
getData().then((data) => {
setArrData(data);
});
}, []); // <-- empty dependency so effect called once on mount
Additionally, since arrData is initially undefined, arrData[0] is likely to throw an error. You may want to provide valid initial state, and a fallback value in case the first element is undefined, so you don't attempt to access properties of an undefined object.
const [arrData, setArrData] = useState([]);
...
const data = arrData[0] || {}; // data is at least an object
return (
<GifCompoment
id={data.id}
name={data.name}
activeTimeTo={data.activeTimeTo}
activeTimeFrom={data.activeTimeFrom}
requiredPoints={data.requiredPoints}
imageUrl={data.imageUrl}
/>
);
You should call state setter insede of Promise
function App() {
const [arrData, setArrData] = useState();
function getData() {
fetch("/api/hidden")
.then((res) => res.json())
.then((data) => setArrData(data));
}
useEffect(() => {
console.log("if it works, this line should be shown");
getData();
}, []);
return ...
}
By combining the answer from Drew Reese and Artyom Vancyan, I have solved my problem. I think the key points are setState right in the then function .then((data) => setArrData(data)) ,don't put the dependency in the useEffect, and await inside the useEffect. Thank you guy super ultra very much. Big love
useEffect(() => {
console.log("if it works, this line should be shown");
const getData = async () => {
await fetch("hidden API")
.then((ref) => ref.json())
.then((data) => {
setArrData(data);
});
}
getData();
}, []);
function App() {
const [arrData, setArrData] = useState([]);
const [loadData, setLoadData] = useState(false);
const async getData=()=> {
var tmpArrData = [];
await fetch("this API is hidden due to the privacy of the company - sorry")
.then((res) => res.json())
.then((data) => {
console.log("data", data);
setArrData(tmpArrData);
});
console.log("tmpData ", tmpArrData);
return tmpArrData;
}
useEffect(() => {
console.log("if it works, this line should be shown");
const callApi =async()=>{
await getData();
}
}, [arrData]);
const data = arrData[0];
console.log(data);
return (
<GifCompoment
id = {data.id}
name = {data.name}
activeTimeTo = {data.activeTimeTo}
activeTimeFrom = {data.activeTimeFrom}
requiredPoints = {data.requiredPoints}
imageUrl = {data.imageUrl}
/>
);
}
export default App;
Page will be rendered three to four times it's normal.

navigator.geolocation coords, used as params in the api url, load after the weatherAPI has already been called

I am using navigator.geolocation to get the latitude and longitude of the user. I am then using latitude and longitude as query params to fetch the WeatherAPI so that my website will show the user the weather in his exact town.
The problem is, I can see from the console log that the fetch() function (which I have stored inside useEffect() runs before the navigator.geolocation, resulting in the API fetching me a location with coordinates 0, 0.
I have tried using the fetch inside the geolocation function, as well as async/await, but since it's already inside useEffect, it won't work.
Any ideas on how I could fix this?
const [latitude, setLatitude] = useState(0)
const [longitude, setLongitude] = useState(0)
const [location, setLocation] = useState([])
navigator.geolocation.getCurrentPosition( (position) => {
setLatitude(position.coords.latitude)
setLongitude(position.coords.longitude)
})
useEffect(() => {
fetch(`https://api.weatherapi.com/v1/forecast.json?key=db698b5e650a441fae6190451221401&q=${latitude},${longitude}&days=1&aqi=yes&alerts=yes`)
.then(response => response.json())
.then(data => {
const locationData = data;
setLocation(locationData);
});
}, [])
const [location, setLocation] = useState([])
useEffect(() => {
navigator.geolocation.getCurrentPosition( (position) => {
fetch(`https://api.weatherapi.com/v1/forecast.json?key=db698b5e650a441fae6190451221401&q=${position.coords.latitude},${position.coords.longitude}&days=1&aqi=yes&alerts=yes`)
.then(response => response.json())
.then(data => {
const locationData = data;
setLocation(locationData);
});
})
}, [])

How to wait for value before running fetch?

Edit: I ended up using axios instead of fetch and it works great. Just removed the response.json() and switch fetch to axios.get.
my first post here with what is probably a pretty easy question. I am trying to get the lat and long values to actually be something before being fed into the URL. Most of the time I get an error returned for a bad request because the lat and long values haven't propagated yet.
Thanks!
Code is here (edited out API keys):
const fetchData = async () => {
navigator.geolocation.getCurrentPosition(function (position) {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
const url =
await `https://api.openweathermap.org/data/2.5/weather/?lat=${lat}&lon=${long}&units=metric&APPID=DELETED`;
await fetch(url)
.then((response) => {
return response.json();
})
.then((result) => {
setData(result);
})
.catch(console.error);
};
fetchData();
}, [lat, long]);
It seems that lat and long are set in the useEffect using them. You should probably set them before using them in another useEffect.
useEffect(() => {
navigator.geolocation.getCurrentPosition(function (position) {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
}, [])
useEffect(() => {
const fetchData = async () => {
const url = `https://api.openweathermap.org/data/2.5/weather/?lat=${lat}&lon=${long}&units=metric&APPID=DELETED`;
await fetch(url)
.then((response) => {
return response.json();
})
.then((result) => {
setData(result);
})
.catch(console.error);
};
if (lat && long) {
fetchData();
}
}, [lat, long]);
Either you have to store those values in your function or you have to wait until the state is updated. State is asynchronous and this is why you get this error.

React Native - state is returning null after setting state

I'm very much new to react native currently i'm building small app for just getting an idea about this. I'm facing an issue in mapping the data from API. This is the json response returning from the api
{"data":[{"digit":300,"countsum":"52"},{"digit":301,"countsum":"102"},{"digit":302,"countsum":"27"},{"digit":303,"countsum":"201"},{"digit":500,"countsum":"101"}]}
When i tried to map this data i'm facing some issues. I stored the response from API to the state and when i tried to display the state data using map function it's showing the state value is null. This the code i tried till now
const [listdata, setListData] = useState(null)
useEffect(() => {
// Run! Like go get some data from an API.
getListData();
}, []);
const getListData = async () => {
const token = await AsyncStorage.getItem("#userToken")
axios
.get(constants.BASE_URL + "getlist?token=" +token)
.then(response => setListData(response.data))
.catch(error => {
console.log(error);
});
listdata.map(item => <Text>{item.digit}</Text>)
}
Do it like this,
export default function ComponentName () {
const [listdata, setListData] = useState([])
useEffect(() => {
// Run! Like go get some data from an API.
getListData();
}, []);
const getListData = async () => {
const token = await AsyncStorage.getItem("#userToken")
axios
.get(constants.BASE_URL + "getlist?token=" +token)
.then(response => setListData(response.data))
.catch(error => {
console.log(error);
});
}
return (<>
listdata.map(item => <Text>{item.digit}</Text>)
</>
);
}
You have to wait the fetch execution and later do the list map.
// wait for it
await axios
.get(constants.BASE_URL + "getlist?token=" +token)
.then(response => setListData(response.data))
.catch(error => {
console.log(error);
});
listdata.map(item => <Text>{item.digit}</Text>)
If you want to map the data then do that inside return statement of your code ,like:
return(
{listData?listdata.map(item => return <Text>{item.digit}</Text>):""}
);
This is a sample of a meant in my comment above:
Try console.log listdata at this stage, you will find that it is still
null, in other words, the value of the updated value of the
listdata:useSate will be ready after the render take place. You can
make another function outside of the current one. then use useEffect
with listdata to update your text views
const [listdata, setListData] = useState(null)
useEffect(() => makeRemoteRequest(), [listdata])
makeRemoteRequest = () => {
const url = `your-url-of-data-here`;
fetch(url)
.then(res => res.json())
.then(res => {
setListData(res.data);
})
.catch(error => {
console.log(error)
});
};
You could try the following:
const [listdata, setListData] = useState([])
useEffect(() => {
// Run! Like go get some data from an API.
getListData();
}, []);
const getListData = async () => {
const token = await AsyncStorage.getItem("#userToken")
try {
const dataResponse = await axios.get(constants.BASE_URL + "getlist?token=" +token);
setListData(dataResponse.data || [] );
} catch(error) {
console.log(error);
}
}
return (<>
listdata.map(item => <Text>{item.digit}</Text>)
</>);

Resources