React: useState(): Fetching data during an ongoing fetch does not work - javascript

I want to display a list of the New York Times bestsellers by fetching the data via an API. But, the covers' links have to be fetched from the Google Books API, since the NYT API does not provide them.
Therefore, I want to map a list with the fetched data. In the same step in which I fetch the data from the NYT API, the data from the Google Books API shall be fetched, too. So, I use the await keyword to let the code wait for the second fetch to be finished.
But, unfortunately, the list does not update though the promise has been fulfilled, and the data has been fetched. The bestsellerlist variable is an array of fulfilled promises, but no "real array" I could access.
I guess it should be possible to split the fetch, and create the list out of two arrays: One with the NYT API data, and one with the Google Books API data. Nevertheless, I wonder how I can fix the code, so I can fetch all the data and create the list in one step.
const fetchLinkCover = async (isbn) => {
const res = await fetch(googleApi(isbn));
const json = await res.json();
return json.items[0].volumeInfo.imageLinks.smallThumbnail;
};
useEffect(() => {
fetch(nytimesApi)
.then((res) => res.json())
.then((json) => {
const fetchedBookList = json.results.books;
setBestsellerList(
fetchedBookList.map(async (item) => {
const linkCover = await fetchLinkCover(item.isbns[0].isbn13);
return {
title: item.title,
authors: item.author,
ranking: item.rank,
cover: linkCover,
};
})
);
});
}, []);
console.log(bestsellerList);
console after executing the code

.map doesn't do anything special with promises. If you return a promise from your map function (and async functions always return promises), then .map will create an array of promises. It won't wait for those promises to resolve.
To wait for an array of promises, you can use Promise.all to combine them together into a single promise. Then you either call .then on that promise, or you put your code in an async function and await it.
With .then:
fetch(nytimesApi)
.then((res) => res.json())
.then((json) => {
const fetchedBookList = json.results.books;
const promises = fetchedBookList.map(async (item) => {
const linkCover = await fetchLinkCover(item.isbns[0].isbn13);
return {
title: item.title,
authors: item.author,
ranking: item.rank,
cover: linkCover,
};
});
return Promise.all(promises);
})
.then((books) => {
setBestsellerList(books);
});
Or with async/await:
useEffect(() => {
const fetchBooks = async () => {
const res = await fetch(nytimesApi);
const json = await res.json();
const fetchedBookList = json.results.books;
const promises = fetchedBookList.map(async (item) => {
const linkCover = await fetchLinkCover(item.isbns[0].isbn13);
return {
title: item.title,
authors: item.author,
ranking: item.rank,
cover: linkCover,
};
});
const books = await Promise.all(promises);
setBestsellerList(books);
};
fetchBooks();
}, []);

Related

How to concat multiple responses and set all response in an array [React JS]

I am doing an API call which is returning IDs and based on number of ids I am doing another call and trying to combine the responses but I am stuck with async issues.
const SearchUser = async () => {
try {
const response = await getSearchUsers();
const ids = response.data?.map((user) => user.userId);
await ids.forEach(async (id) => {
const result = await getUserInfo(id);
setRNOUsers(...result);
// combine result in one state
});
} catch (error) {
setSearching(false);
}
};
useEffect(() => {
SearchUser();
console.log('RNOUsers', RNOUsers); // this is empty and runs even before callng api
}, []);
How can handle this?
You can use Promise.all to wait for all responses, and then set them together with setRNOUsers
const SearchUser = async () => {
try {
const response = await getSearchUsers();
const ids = response.data?.map((user) => user.userId);
const responses = await Promise.all(ids.map(id => getUserInfo(id)))
setRNOUsers(...responses.flatMap(x => x));
} catch (error) {
setSearching(false);
}
};
useEffect(() => {
SearchUser();
console.log('RNOUsers', RNOUsers);
}, []);
Side note, the problem with console.log('RNOUsers', RNOUsers) is setRNOUsers (initialized by useState) is asynchronous. Besides that, your API calls are also asynchronous, so you cannot get values from RNOUsers immediately in useEffect. If you want to see data in that log, you should wait until the state is updated and your component gets re-rendered with your latest data.

Performing an api call on each item in an array and appending the result to a new array

I have an array and I am trying to perform an api call on each item.
Like so -
shoppingData.items.map(item => {
getItemInformation(item.id)
.then(response => response.json())
.then(data => {
JSON.parse(data.text);
});
getItemInformation is my api call -
export async function getItemInformation(id) {
try {
const req = await fetch(`**api url**`);
return await req;
} catch (e) {
console.error(e);
return 'Error';
}
}
However, once I have parsed the data, I would like to append it to a new array. This new array will then be used to render a component later down the page like so -
{newArray?.map((item, index) => (
<ShoppingItem key={index} itemDescription={item.description} itemCatergory={item.catergory} />
))}
Im having issues doing this as I have been trying to do it in a useEffect as ideally I need it to happen when the page renders. Furthermore, I tried having newArray as a state e.g const [newArray, setNewArray] = useState([]) but because I am appending items to an array, setNewArray wouldn't allow me to do this.
use Promise.all
const allItems = await Promise.all(
shoppingData.items.map(
item => getItemInformation(item.id)
.then(response => response.json())
)
);
You could also simplify this a bit by putting all the "asyncy" stuff into your getItemInformation method
const allItems = await Promise.all(
shoppingData.items.map(
item => getItemInformation(item.id)
)
);
and
export async function getItemInformation(id) {
try {
const req = await fetch(`**api url**`);
const json = await req.json();
return json;
} catch (e) {
console.error(e);
return 'Error';
}
}
Live example usiong JSONPlaceholder demo api:
async function getItemInformation(id) {
console.log("getItemInformation",id);
try {
const req = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
const json = await req.json();
return json;
} catch (e) {
console.error(e);
return 'Error';
}
}
(async function testIt() {
const shoppingData = {
items: [{
id: 1
}, {
id: 2
}, {
id: 3
}]
};
const allItems = await Promise.all(
shoppingData.items.map(
item => getItemInformation(item.id)
)
);
console.log(allItems);
})()
You can easily call setNewArray(allItems) in react useEffect using this code (basically where I did consolew.log(allItems) above.
I am not sure how do you append the data to your array while you are calling async function in .map without waiting for the result.
I assume you may be trying something like this:
const parsedData = shoppingData.items.map(item => {
getItemInformation(item.id)
.then(response => response.json())
.then(data => {
JSON.parse(data.text);
});
setNewArray(parsedData);
There are two points to note. Firstly getItemInformation() is called on each item async so the order of parsedItem may not be what your expected. Secondly setNewArray() is called while parsedData's async calls may not finish.
One possible way you can try, if you really want to use .map, is using Promise.all(). Promise.all() resolves when all its promises are resolved:
const getParsedData = async() => {
return Promise.all(shoppingData.items.map(item => getItemInfo.......));
}
const data = await getParsedData();
then append data to your array.
Another simpler solution is using for-loop and await:
for (let i=0;i<shoppingData.items.length;i++){
const parsedItem = await getItemInformation(shoppingData.items[i]).........
newArray.push(parsedItem);
}

Array of Promises still say pending after chaining on then function

I am currently working on a small express app where I am using axios to send/receive data from a github API. I am using insomnia to test sending the data as JSON to the github API in order to find specified user(s) profile information. This is how I am sending the data:
{
"developers": ["person", "person"]
}
Here is my code:
let results = req.body.developers
results.map(async d => {
await axios.get(`https://api.github.com/users/${d}`)
.then((response => {
console.log(response)
}))
.catch((err) => {
console.log(err)
})
})
let out = results.map(r => ({ name: r.data.name, bio: r.data.bio }));
return res.send(JSON.stringify(out));
When I make a request, I receive my promises in an array, but they still say 'Pending'. I thought that by chaining the then function to my get request within the results.map would solve this issue. What is wrong with my code?
You should use await Promise.all() on results, and assign the responses to some variable to use in your out array.
const results = req.body.developers
const responses = await Promise.all(results.map(async d => {
const response = await axios.get(`https://api.github.com/users/${d}`);
console.log(response);
return response;
});
const out = responses.map(({ data } => ({ name: data.name, bio: data.bio }));
return res.send(JSON.stringify(out));
For error handling, you can wrap the Promise.all call in a try/catch block, and handle the rejected promise there.

How to fetch data on every element in an array using array.map method

I want to fetch data for every object in an array and return an array of new objects with the previous and newly fetched data.I got stucked on getting my result array as my function is returning an array of resolved undefined promises.
I am using a flight search api thats using the apca function for fetching
export const searchApcaLocation = async (dataArr,setDeals) => {
const promises = await dataArr.map(async item => {
apca.request(item.destination);
apca.onSuccess = (data) => {
return fetch('http://localhost:3050/googlePlaceSearch',{
method:"post",
headers:{'Content-Type':'application/json'},
body:JSON.stringify({
cityName:data.airports[0].city
})
})
.then(res => res.json())
.then(imagelinkData => {
const locationObject = {
data: item,
imagelink: imagelinkData.link
}
return locationObject
})
.catch(err => console.log('error on image search',err))
};
apca.onError = (data) => {
console.log('error',data)
};
})
const results = await Promise.all(promises)
return results
}
can someone guide me please on what am I doing wrong?
edit:
as I am trying to fix it realized the problem is I am not returning anything in my map function but if trying to return the apca.onSuccess I am getting an array of functions
just return is missing before fetch function. since you're not returning your promise result it's giving undefined.
export const searchApcaLocation = async (dataArr,setDeals) => {
const promises = await dataArr.map(async item => {
apca.request(item.destination);
apca.onSuccess = (data) => {
return fetch('http://localhost:3050/googlePlaceSearch',{
method:"post",
headers:{'Content-Type':'application/json'},
body:JSON.stringify({
cityName:data.airports[0].city
})
})
.then(res => res.json())
.then(imagelinkData => {
const locationObject = {
data: item,
imagelink: imagelinkData.link
}
return locationObject
})
.catch(err => console.log('error on image search',err))
};
apca.onError = (data) => {
console.log('error',data)
};
})
const results = await Promise.all(promises)
return results
}
The issue in your case might be, that you are using async/await and then blocks together.
Let me sum up what is happening :
1) you await dataArray.map
2) within the map callback, you use the onSuccess method of apca
3) within this method you are using then blocks which won't await until you got a response.
At this point where you return the locationObject, your function already reached the return statement and tries to return results.
But results are of course undefined because they never get resolved at all.
Also, keep in mind that your function returns another promise because you used async/await which you have to resolve where you imported it.
Cheers :)

How do I combine two fetch request into the same array?

I am trying to combine two fetch requests in one call so I can get all the data in the same array.
I have tried the Promise.all method but I don't know if it is the right way to do it.
getWeather = async (e) => {
e.preventDefault();
const city = e.target.elements.city.value;
//const api_call = await
const promises = await Promise.all([
fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&APPID=${API_KEY}`),
fetch(`http://api.openweathermap.org/data/2.5/forecast?q=${city}&units=metric&APPID=${API_KEY}`)
])
const data = promises.then((results) =>
Promise.all(results.map(r => r.text())))
.then(console.log)
The code actually works and I'm getting data back but I can't understand the json response.
(2) ["{"coord":{"lon":-5.93,"lat":54.6},"weather":[{"id"…7086601},"id":2655984,"name":"Belfast","cod":200}", "{"cod":"200","message":0.0077,"cnt":40,"list":[{"d…on":-5.9301},"country":"GB","population":274770}}"]
How should I set the state?
My state was set like this, with only one call.
if (city) {
this.setState({
temperature: data[0].main.temp,
city: data[0].name,
Is there a better way to do it?
I'd do:
getWeather = async (e) => {
e.preventDefault();
const fetchText = url => fetch(url).then(r => r.json()); // 1
const /*2*/[weather, forecast] = /*3*/ await Promise.all([
fetchText(`.../weather`),
fetchText(`.../forecast`)
]);
this.setState({ temperature: weather.temp, /*...*/ });
}
1: By using a small helper, you don't have to call Promise.all twice. With this both requests are done in parallel (and you should use .json() as you want to parse it as JSON).
2: Through array destructuring you can easily get back the promises results.
3: Through awaiting you get the actual benefit from async functions: You don't need nested .then chains
You can write in following way which is cleaner approach and will have your data categorised
const success = res => res.ok ? res.json() : Promise.resolve({});
const weather = fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&APPID=${API_KEY}`)
.then(success);
const forecast = fetch(`http://api.openweathermap.org/data/2.5/forecast?q=${city}&units=metric&APPID=${API_KEY}`)
.then(success);
return Promise.all([weather, forecast])
.then(([weatherData, forecastData]) => {
const weatherRes = weatherData;
const ForecastRes = forecastData; // you can combine it or use it separately
})
.catch(err => console.error(err));
}

Categories

Resources