Nested axios API call causes trouble on updating state React - reactjs

I'm trying to build a simple app that fetches data from an API and shows them. I have a scenario in which I have to fetch the IDs of some items, and then for every ID make an API call to get the details. I want to set the array of fetched details as a state, and I can actually do it, but the view does not update and I don't understand why... I guess I'm doing a mess with asynchronous calls, as always...
updateSearchResults() is a state setter passed as a prop from the upper level component, and the holder of the state is that same upper level component.
async function handleSubmit(event) {
event.preventDefault();
if(name) {
let res = await axios.get(`https://www.foo.bar/search`);
if(res.data.items !== null) {
const filteredItems = filterItems(res.data.items);
updateSearchResults(filteredItems); //this works as expected
}
} else {
let res = await axios.get(`https://www.foo.bar/anothersearch`);
if(res.data.items !== null) {
let items= [];
res.data.items.forEach(async item => {
const resDetails = await axios.get(`https://www.foo.bar/getdetails`);
items.push(resDetails.data.items[0]);
})
console.log(items); //this prints the expected result
updateSearchResults(items); //this updates the state but not the view
}
}
}

...
const items= await Promise.all(res.data.items.map(async item => {
const resDetails = await axios.get(`https://www.foo.bar/getdetails`);
return resDetails.data.items[0];
}));
console.log(items); //this prints the expected result
updateSearchResults(items);

You can modify your code to something like this:
async function handleSubmit(event) {
event.preventDefault();
if(name) {
let res = await axios.get(`https://www.foo.bar/search`);
if(res.data.items !== null) {
const filteredItems = filterItems(res.data.items);
updateSearchResults(filteredItems); //this works as expected
}
} else {
let res = await axios.get(`https://www.foo.bar/anothersearch`);
if(res.data.items !== null) {
let items= [];
for await (const item of res.data.items) {
const resDetails = await axios.get(`https://www.foo.bar/getdetails`);
items.push(resDetails.data.items[0]);
}
console.log(items); //this prints the expected result
updateSearchResults(items); //this updates the state but not the view
}
}
}

Related

API call was success but an alert shows as unsuccessful in React

I call an API call when a button is clicked. in the console, it shows as the API call was a success.
Then upon the successful call, I call a print handler to print the screen. But the first time the button clicks, it shows as unsuccessful & when I click again it shows as successful.
Following is my code.
const ConfirmBooking = async() =>{
console.log("child",seatsSelected)
const adultTicketCount = seatCount - counter
console.log("adult",adultTicketCount)
const firstName = "Ryan"
const lastName = "Fonseka"
const email = "ryan#343.com"
const mobileNumber = "333333"
const customerDetails = {
firstName:firstName,
lastName:lastName,
email:email,
mobileNumber:mobileNumber
}
let seatDetails = []
let seatCatId = 2761
seatDetails.push({
seatCategoryId: seatCatId,
seatNumbers:seatNumbers,
childCount: counter,
adultCount: adultTicketCount
})
console.log(seatDetails)
let mounted = true
await BookingRequest(seatDetails,customerDetails) // this is the API call
.then(data =>{
if(mounted){
setBooking(data)
}
})
console.log(booking)
const status = booking
console.log(status.success)
if((booking.success) === true){
await printHandleOpen(booking)
} else {
alert("Booking Failed")
}
}
It seems that the problem could be on the line in the API call where you call setBooking(data). This will schedule a state update, but the update will only occur after this function is popped off the call stack, so in the if((booking.success) === true) line, this will only evaluate as expected on the second time through.
edit: adding suggested .then code block
.then(data => {
if(mounted){
setBooking(data)
await printHandleOpen(booking
}
})
.catch(err => {
alert("Booking Failed")
})
and then you can remove that if...else block that fires those methods later in the code.
// Get this out of the function 'ConfirmBooking'
if((booking.success) === true){
await printHandleOpen(booking)
} else {
alert("Booking Failed")
}

Async .map() not working within React hook

I cannot understand why the .map() function is somehow not being called in this function. I'm trying to call my API asynchronously within the componentDidMount() hook, but it seems that the array method never gets called.
async componentDidMount() {
try {
const response = await fetch(url);
const data = await response.json();
console.log(`Returned: ${data.zipCodes.length} results`);
console.log(data.zipCodes[0]);
const geoPoints = data.zipCodes;
geoPoints
.filter(geoPoint => geoPoint.Recipient_Postal_Code !== "")
.map(function (geoPoint) {
const location = zipToLocation.find(element => element.fields.zip === geoPoint.Recipient_Postal_Code);
if (!location){
return;
}
if(!location.hasOwnProperty('fields')) {
return;
}
const lat = location.fields.latitude;
const lng = location.fields.longitude;
const result = {
lat: lat,
lng: lng,
trackingNumber: geoPoint.Shipment_Tracking_Number
}
console.log(result); //OK!
return result;
});
console.log(`Mapped ${geoPoints.length} geoPoints`);
console.log(geoPoints); //same data as data.zipCodes ??
this.setState({data: geoPoints})
} catch(error) {
console.log(error);
}
}
Is is somehow being disrupted by React?
.map doesn't edit the input array, it returns a new array that is created from the old one.
You want to put:
const geoPoints = data.zipCodes
.filter(...)
.map(...);

ReactJs Unable to setSate in componentDidMount from async function

I'm calling an async function (getData()) in componentDidMount, and I'm trying to use this.setState with result of that function.
componentDidMount() {
let newData = getData();
newPodData.then(function (result) {
console.log('result', result)
this.setState({result})
})
}
However, I'm having issues getting my state to properly update. Some additional context - I'm trying to set my initial state with data I am receiving from a database. Is my current approach correct? What's the best way to accomplish this? Here's my async function for more context:
const getTeamData = async () => {
const getTeamMembers = async () => {
let res = await teamMemberService.getTeamMembers().then(token => { return token });
return res;
}
const getActiveTeams = async () => {
let res = await teamService.getActiveTeams().then(token => { return token });
return res;
}
const teamMemberResult = await getTeamMembers()
const activeTeamsResult = await getActiveTeams();
// get team member data and add to teamMember object
let teamMemberData = teamMemberResult.reduce((acc, curr) => {
acc.teamMembers[curr.id] = curr;
return acc;
}, {
teamMembers: {}
});
// get team ids and add to teamOrder array
let activeTeamsData = activeTeamsResult.map(team => team.id)
let key = 'teamOrder'
let obj = []
obj[key] = activeTeamsData;
const newObject = Object.assign(teamMemberData, obj)
return newObject;
}
export default getTeamData;
Changing the function inside the then handler to an arrow function should fix it. e.g:
componentDidMount() {
let newData = getData();
newPodData.then((result) => {
console.log('result', result)
this.setState({result})
})
}
But I'll like to suggest a better way to write that.
async componentDidMount() {
let result = await getData();
this.setState({result})
}

React Hooks - Set state based on multiple async calls in loop

My below effect is trying to accomplish the following:
I need to make 3 concurrent XHR requests for jobs, postings, and sale items.
When the data returns, I need to sort the records into open, closed, and 'therest' arrays which I then append to a 'running_total' array.
Once all of the XHR requests have been completed and sorted, I want set the running_total array to state.
Unfortunately, my below code does not seem to respect async / await and once the effect has finished running, I end up with an empty array. Any ideas what I might be doing wrong?
useEffect(() => {
const types = ["jobs", "postings", "sale_items"];
async function getEntityReferencesAsync() {
let running_total = [];
types.forEach(
function(type) {
let open = [];
let closed = [];
let therest = [];
const request = await get_data(
`https://myapi.com.com/${type}/${props.id}`
);
response.then(
function(result) {
const result_array = result.data.records;
result_array.forEach(
function(item) {
item["type"] = type;
if (item.status === "open") {
open.push(item);
} else if (item.status === "closed") {
closed.push(item);
} else {
therest.push(item);
}
}.bind(this)
);
running_total = [
...running_total,
...open,
...closed,
...therest
];
}.bind(this)
);
}.bind(this)
);
return running_total;
}
async function getSortedData() {
const sorted_array = await getEntityReferencesAsync();
setEntityReferencesData(sorted_array);
}
getSortedData();
}, [props.id]);
FYI my get_data function looks like:
async function get_data (endpoint, params) {
if (params === null) {
return await axios.get(endpoint)
} else{
return await axios.get(endpoint, {params: params});
}
};

MobX don't update react DOM in fetch promise callback

I am trying to update a react dom by changing an observable mobx variable inside a fetch callback in a react typescript app but mobx don't show any reaction on variable change.
I define my variable like this:
#observable data:any = []
and in my constructor i change data value:
constructor(){
this.data.push(
{
count:0,
dateTime:'2017'
})
this.getData();
}
its work fine and update dom properly as expected.
in getData() method i write a fetch to retrive data from server :
#action getData(){
this.data.push(
{
count:1,
dateTime:'2018'
})
fetch(request).then(response=>response.json())
.then(action((data:Array<Object>)=>{
this.data.push(data)
console.log(data)
}));
}
so my view now shows 2 value the 2017 and 2018 object data but the 2019 data that I get from the server is not showing. the log shows the correct values and variable filled in a right way but mobx don't update view after I set any variable in fetch function callback and I don't know why?
p.s: I do the same in ECMA and there was no problem but in typescript mobx act differently
Check my approach:
import { action, observable, runInAction } from 'mobx'
class DataStore {
#observable data = null
#observable error = false
#observable fetchInterval = null
#observable loading = false
//*Make request to API
#action.bound
fetchInitData() {
const response = fetch('https://poloniex.com/public?command=returnTicker')
return response
}
//*Parse data from API
#action.bound
jsonData(data) {
const res = data.json()
return res
}
//*Get objects key and push it to every object
#action.bound
mapObjects(obj) {
const res = Object.keys(obj).map(key => {
let newData = obj[key]
newData.key = key
return newData
})
return res
}
//*Main bound function that wrap all fetch flow function
#action.bound
async fetchData() {
try {
runInAction(() => {
this.error = false
this.loading = true
})
const response = await this.fetchInitData()
const json = await this.jsonData(response)
const map = await this.mapObjects(json)
const run = await runInAction(() => {
this.loading = false
this.data = map
})
} catch (err) {
console.log(err)
runInAction(() => {
this.loading = false
this.error = err
})
}
}
//*Call reset of MobX state
#action.bound
resetState() {
runInAction(() => {
this.data = null
this.fetchInterval = null
this.error = false
this.loading = true
})
}
//*Call main fetch function with repeat every 5 seconds
//*when the component is mounting
#action.bound
initInterval() {
if (!this.fetchInterval) {
this.fetchData()
this.fetchInterval = setInterval(() => this.fetchData(), 5000)
}
}
//*Call reset time interval & state
//*when the component is unmounting
#action.bound
resetInterval() {
if (this.fetchInterval) {
clearTimeout(this.fetchInterval)
this.resetState()
}
}
}
const store = new DataStore()
export default store
as #mweststrate mentioned in the comments, it was an observer problem and when I add #observer on top of my react class the problem get fixed

Resources