how to handle delay with setting useState with API data - reactjs

I am not the first one to have this problem.
but sadly, nothing of what i saw is working for me.
I wanna get data from API.
use the data to call another API.
and than return the data in or whatever.
so what happens is, the first fetch is going fine. im trying to set it inside a state.
but when the SECOND API runs, the STATE i need, is still empty... it hasnt been updated.
so the component cant render something that doesnt exist. so it crashes.
and also, something here causes multiple renders...
here is the code:
const SecondDisplay = () => {
const [firstData, setFirstData] = useState("")
const [secondData, setSecondData] = useState("")
const [errors, setErrors] = useState("")
const [loading, setLoading] = useState(true)
useEffect(() => {
navigator.geolocation.getCurrentPosition(function(position) {
// getLocation(32.0853, 34.7818)
fetch(`https://dataservice.accuweather.com/locations/v1/cities/geoposition/search?apikey=AZvK08ugMNLlAGAwDD9GQGj108Tm8OIP&q=${position.coords.latitude}%2C${position.coords.longitude}&language=en-us&details=false&toplevel=false`)
.then(res => {
if(!res.ok){
throw Error("Sorry, something went wrong. please try again later.")
}
return res.json()
})
.then(data => {
setFirstData({key: data.Key, name: data.AdministrativeArea.EnglishName})
})
.catch(error => {
setErrors(error.message)
setLoading(false)
})
})
}, [])
useEffect(() => {
if(firstData !== ""){
third(firstData.key)
}
}, [firstData])
let third = key => {
fetch(`http://dataservice.accuweather.com/currentconditions/v1/${key}?apikey=AZvK08ugMNLlAGAwDD9GQGj108Tm8OIP&language=en-us&details=true HTTP/1.1`)
.then(res => {
if(!res.ok){
throw Error("Sorry, something went wrong. please try again later.")
}
return res.json()
})
.then(data => {
setSecondData(data)
setLoading(false)
})
.catch(error => {
setErrors(error.message)
setLoading(false)
})
}
return(<p>{secondData.blabla}</p>)

What you can do is use a single useEffect() hook, and simply continue the promise chain once you get the response from the first request.
Here's a simplified example:
fetch(url)
.then(res => {
if (!res.ok) throw Error(msg)
return res.json()
})
.then(data => {
setFirstData(data)
return fetch(secondUrl, paramsBasedOn(data))
})
.then(res => {
if (!res.ok) throw Error(msg)
return res.json()
})
.then(data => {
setSecondData(data)
})
.catch(err => setError(err.message))

Related

Fetching an array of objects from POKEAPI using REACT.js and AXIOS {Answered}

I chose to start learning API handling with POKEAPI. I am at a step where I need to get the flavor_text of each pokemon (the description let's say) but I can't for some reason.
Here is the JSON structure for one specific pokemon: https://pokeapi.co/api/v2/pokemon-species/bulbasaur.
And here is my useEffect trying to get it. The line fetching the habitat works and displays on my website so I guess my issue comes from my map in setDescription but I can't be sure.
export default function Card({ pokemon }, { key }) {
const src = url + `${pokemon.id}` + ".png";
const [habitat, setHabitat] = useState(null);
const [descriptions, setDescriptions] = useState([]);
useEffect(() => {
const controller = new AbortController();
axios
.get(url2 + `${pokemon.name}`, { signal: controller.signal })
.then((res) => setHabitat(res.data.habitat.name))
.then((res) =>
setDescriptions(
res.data.flavor_text_entries.map((ob) => ob.flavor_text)
)
)
.catch((err) => {
if (axios.isCancel(err)) {
} else {
console.log("warning your useEffect is behaving");
}
});
return () => {
// cancel the request before component unmounts
controller.abort();
};
}, [pokemon]);
I tried console logging descriptions or descriptions[0] but that doesn't work.
Since you only setting up the state from those data and it doesn't looks like the second result need to wait the result from the first to perform you can do both on the same response/promise :
useEffect(() => {
const controller = new AbortController();
axios
.get(url2 + `${pokemon.name}`, { signal: controller.signal })
.then((res) => {
setHabitat(res.data.habitat.name))
const flavorTextEntrieList = res.data.flavor_text_entries;
setDescriptions(flavorTextEntrieList.map((ob) => ob.flavor_text))
})
.catch((err) => {
if (axios.isCancel(err)) {
} else {
console.log("warning your useEffect is behaving");
}
});
return () => {
// cancel the request before component unmounts
controller.abort();
};
}, [pokemon]);
Each then need to return something to be handled in next chainable then. Replace .then((res) => setHabitat(res.data.habitat.name)) with .then((res) => { setHabitat(res.data.habitat.name); return res; })

How do I use the result of a useEffect fetch in a subsequent fetch?

I can't figure out how to pass a field's data from one useEffect fetch query (using GROQ) to a second useEffect fetch query using a REST API with URL parameters.
const [airline, setAirline] = useState(null);
const [airport, setAirport] = useState(null);
const { slug } = useParams();
const url = "https://aviation-edge.com/v2/public/airportDatabase?key={myKeyHere}&codeIataAirport=";
useEffect(() => {
sanityClient
.fetch(
`*[slug.current == $slug]{
...
hubIataCode,
...
}`,
{ slug }
)
.then((data) => setAirline(data[0]))
.catch(console.error);
}, [slug]);
useEffect(() => {
fetch(`${url}${airline.hubIataCode}`)
.then((response) => response.json())
.then((data) => setAirport(data));
}, []);
Perhaps the two need combining?
To my surprise there isn't much information on using data from the first API call on the second with useEffect, or perhaps I can't word my search correctly.
Option 1 - Add airline as dependency to the 2nd useEffect and bail out if it's null:
useEffect(() => {
if(!airline) return;
fetch(`${url}${airline.hubIataCode}`)
.then((response) => response.json())
.then((data) => setAirport(data));
}, [airline]);
Option 2 - combine requests to a single useEffect using async/await:
useEffect(() => {
const fetchData = async () => {
try {
const [airline] = await sanityClient.fetch(`*[slug.current == $slug]{...hubIataCode,...}`, { slug });
const airport = await fetch(`${url}${airline.hubIataCode}`).then((response) => response.json());
setAirline(airline);
setAirport(airport);
} catch (e) {
console.error(e);
}
};
fetchData();
}, [slug]);

users array hook is not updating with all the list items

So I'm creating a simple MERN App, backend is working properly, but when working with useState hook in frontend is causing issues.
what im trying to do is to fetch "users" data(an array of object with field username) from backend endpoints, and updating the users array which is a hook, but it only updates with the last itm of the incoming username and not list of all usernames!!
code for fetching and updating the hook:
const [users, setUsers] = useState([]);
const getUsers = () => {
fetch("http://localhost:5000/users")
.then(res => res.json())
.then(data => {
console.log(data); //line 17
data.map((itm) => {
console.log([itm.username]) //line 19
setUsers([...users, itm.username])
})
})
.catch(err => console.log(err))
}
useEffect(() => {
getUsers();
}, [])
console.log(users); //line 30
what I want is to get a list of usernames in the "users" state!
something like this:
users = ["spidey", "thor", "ironman", "captain america"]
console.log is also not showing any errors...
console window
pls help, can't figure out where it's getting wrong?
The issue is two-fold, first you are using Array.prototype.map to iterate an array but are issuing unintentional side-effects (the state updates), and second, you are enqueueing state updates in a loop but using standard updates, each subsequent update overwrites the previous so only the last enqueued update is what you see in the next render.
Use either a .forEach to loop over the data and use a functional state update to correctly update from the previous state.
const getUsers = () => {
fetch("http://localhost:5000/users")
.then(res => res.json())
.then(data => {
console.log(data);
data.forEach((itm) => {
console.log([itm.username]);
setUsers(users => [...users, itm.username]);
})
})
.catch(err => console.log(err));
}
Or use the .map and just map data to the array you want to append to the users state.
const getUsers = () => {
fetch("http://localhost:5000/users")
.then(res => res.json())
.then(data => {
console.log(data);
setUsers(users => users.concat(data.map(itm => itm.username)));
})
.catch(err => console.log(err));
}
you can set the map result in a variable after that you can call the useState on it.
const [users, setUsers] = useState([]);
const getUsers = () => {
fetch("http://localhost:5000/users")
.then(res => res.json())
.then(data => {
console.log(data); //line 17
const userNameData = data.map(itm => itm.username)
setUsers(...users, userNameData)
})
.catch(err => console.log(err))
}
useEffect(() => {
getUsers();
}, [])
console.log(users);

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