Async request to firebase inside forEach - javascript

I have an array like that (generatedArray):
[
{
...some data,
[deals],
type: 'uo'
},
{
...some data,
[deals],
type: 'pif',
pifKua: 'some value'
},
...other similar objects
]
If type is 'pif' I want to take snapshot from firebase and put snapshot's value in pifKua field. To do it I have a such function:
handleCreateDocs = () => {
this.generatedArray.forEach( item => {
console.log('itemtype', item.type);
if (item.type === 'pif') {
const stocksRef = firebase.database().ref('clients');
const kua = stocksRef.orderByChild('shortName').equalTo(item.pifKua);
kua.once('value').then( snapshot => {
snapshot.forEach((child) => {
Object.assign(item.pifKua, child.val());
});
})
}
});
this.setState({
kuaCheck: true
}, () => {console.log('kua', this.generatedArray)});
};
But after this function this.generatedArray don't changes. In console i can see 'kua', but this.generatedArray still the same witjout any changes. Help me to fix it please.

You should only call setState until you have all the data available. Right now, you are calling it without waiting for any of the queries to complete. then does not cause your code to pause and wait for the result. It just returns another promise that resolves some time later while your code continues in the forEach loop.
You will need to collect your promises into an an array, and use Promise.all() to wait for them to complete. The general form of your code will be more like this:
handleCreateDocs = () => {
const promises = [];
this.generatedArray.forEach( item => {
console.log('itemtype', item.type);
if (item.type === 'pif') {
const stocksRef = firebase.database().ref('clients');
const kua = stocksRef.orderByChild('shortName').equalTo(item.pifKua);
const promise = kua.once('value').then( snapshot => {
snapshot.forEach((child) => {
Object.assign(item.pifKua, child.val());
});
});
promises.push(promise);
}
});
Promise.all(promises).then(() => {
console.log("all queries complete");
this.setState({
kuaCheck: true
}, () => {console.log('kua', this.generatedArray)});
});
};

Related

Firestore get subcollection data with promises

I'm trying to get the data from my subcollections, the things is I need to do it with Promises (If I don't, I can't get the data from the cache)
Here how I am actually doing :
bookStores = db.collection("bookstores");
bookStores.onSnapshot((snapshot) => {
snapshot.docChanges().forEach((change) => {
bookStoresIds.push(change.doc.id); // I use a list so a can iterate on IDs for subs
// Doing stuff
});
bookStoresIds.forEach(bookStoreId => {
const task = db.collection('bookstores').doc(bookStoreId).collection('books')
task.onSnapshot((snapshotTask) => {
snapshotTask.docChanges().forEach((change) => {
// Doing stuff
});
});
})
I use a list to store the IDs. This version works, but causes me some troubles and I want to use Promises.
Here what I tried :
async function getBookStores(id,) {
const bookStoreIds: string[] = [];
db.collection("bookStores").onSnapshot({ includeMetadataChanges: true }, (snapshot) => {
snapshot.docChanges().forEach((change) => {
// Doing Stuff
});
});
return bookStoreIds;
}
async function getBooks(bookStoreIds) {
bookStoreIds.forEach(bookStoreId => {
const book = db.collection('bookStores').doc(bookStoreId).collection('books')
task.onSnapshot({ includeMetadataChanges: true }, (snapshotTask) => {
snapshotTask.docChanges().forEach((change) => {
// Doing Stuff
});
})
})
}
getBookStores(id)
.then((list) => {
return getBooks(list);
})
The problem is, when it cames to getBooks, the list is empty ... Is somebody have an idea ? 🙏

React wait for one loop (and setState) to finish before another loop

handleSubmit = () => {
this.setState({ modalState: false })
this.state.codeToClass.forEach((code, classId, map) => {
const cr = _.find(this.state.classRoles, { id: classId })
if (code === cr.classCode) {
console.log('here')
this.setState(state => ({
classRoles: state.classRoles.map((cc) => {
console.log(cc.id)
console.log(classId)
console.log(cc.id === classId)
if (cc.id === classId) {
console.log('here1')
return {
...cc,
role: 'TA',
}
}
console.log('what')
return cc
}),
}), ()=> console.log(this.state.classRoles)) //this is called later
} else {
NotificationManager.error('Failed to register as TA.')
}
})
console.log(this.state.classRoles) //this is called first
this.state.classRoles.forEach((c) => {
if (c.role === '') {
api.deleteClassUser(c.id, this.state.user.id)
} else {
api.postAddClass(c.id, this.state.user.id, c.role)
console.log(c)
}
})
EventEmitter.publish('currentlyEnrolled', this.state.classRoles)
}
I'm trying to run the second forEach after the first forEach has finished,i.e. state has been updated. But it keeps running in this order. Is there a way to fix this?
Promise is your friend.
// You map your operations in to Promises.
const promises = this.state.codeToClass.map((code, classId, map) => {
return new Promise(resolve=>{
const cr = _.find(this.state.classRoles, { id: classId })
if (code === cr.classCode) {
console.log('here')
this.setState(state => ({
classRoles: state.classRoles.map((cc) => {
console.log(cc.id)
console.log(classId)
console.log(cc.id === classId)
if (cc.id === classId) {
console.log('here1')
return {
...cc,
role: 'TA',
}
}
console.log('what')
return cc
}),
}), ()=> resolve()) //this is called later
} else {
NotificationManager.error('Failed to register as TA.')
}
})
})
// and then you wait for all of the promises
await Promise.All(promises);
// then continue to execution
There are two options.
Use Promise
Async await
Since map can be used with await, I think
const tempState = this.state.codeToclass;
await tempState.map(...
This way can work :)
this.setState is an asynchronous operation.
You can try something like this:
handleSubmit = () => {
//some code...
this.setState(state => ({
state.codeToClass.forEach((...args) => {
//logic to update the state...
});
}), setClassRoles); //call a function after the state value has updated
}
setClassRoles = () => {
this.state.classRoles.forEach((...args) => {
//your code...
});
EventEmitter.publish('currentlyEnrolled', this.state.classRoles)
}

React & Firebase - making multiple Firebase calls and waiting for all promises to be complete

I would like to change the following code as it reaches Firebases 10 item in an array limit. I want to loop through all teamIds and make a Firebase query for each individual teamId. The issue is, I'm not sure how to do this in a way that it waits until all promises are complete before continuing.
This is the current code;
const unsubscribe = Firebase.firestore().collection('invites').where('teamId', 'in', teamIds).onSnapshot(snapshot => {
const invites = [];
snapshot.docs.forEach(doc => {
const data = doc.data();
if (!invites[data.teamId]) {
invites[data.teamId] = [];
}
invites[data.teamId].push(Object.assign({}, { id: doc.id }, doc.data()));
});
setTeamInvites(invites);
setLoading(false);
setError(false);
});
I've like to change it to something like this;
teamIds.forEach(teamId => {
Firebase.firestore().collection('invites').where('teamId', '==', teamId).onSnapshot(snapshot => {
// Map the results to an array that will be stored in the pageState when all promises are complete
});
});
How can I do this?
I figured out I can do this with Promise.all, this is what I ended up with.
const promises = [];
teamIds.forEach(teamId => {
promises.push(new Promise((resolve, reject) => {
Firebase.firestore().collection('invites').where('teamId', '==', teamId).onSnapshot(snapshot => {
let invites = [];
invites[teamId] = [];
snapshot.docs.forEach(doc => {
const data = doc.data();
invites[data.teamId].push(Object.assign({}, { id: doc.id }, data));
resolve(invites);
});
});
}));
});
Promise.all(promises).then(allInvites => {
// Do what I needed to do with all of the invites here
});

Wait end of two subscribe to make an operation

I have two subscribe like this :
this.birthdays = await this.birthdaySP.getBirthdays();
this.birthdays.subscribe(groups => {
const allBirthdayT = [];
groups.map(c => {
allBirthdayT.push({
key: c.payload.key,
...c.payload.val()
})
})
console.log(allBirthdayT);
});
this.birthdaysInGroups = await this.birthdaySP.getBirthdaysInGroups();
this.birthdaysInGroups.subscribe(groups => {
const allBirthdayB = [];
groups.map(c => {
c.birthdays.subscribe(d => {
d.map(e => {
allBirthdayB.push(e);
})
})
})
console.log(allBirthdayB);
});
I would like to wait the end of this two subscribes to compare allBirthdayB and allBirthdayT arrays (i receive datas in two console.log).
this.birthdaySP.getBirthdays() and this.birthdaySP.getBirthdaysInGroups() are two observable that receive data from firebase.
The first Observable is like that :
async getBirthdays() {
const user = await this.authSP.getUserInfo();
return this.angularFire.list('birthdays', ref => ref.orderByChild('creator_user_id').equalTo(user.uid)).snapshotChanges();
}
I try with forkJoin but i don't know how i can use it to solve this problem
Any tips?
You can use the combineLatest() function.
Example:
combineLatest(observable1$, observable2$)
.subscribe(([observable1, observable2]) => {
console.log(observable1, observable2);
});

ReactJS how to wait for all API calls to be ended in componentDidMount of simple component

I'm using latest react and very basic app which calls 3rd party service API which actually is not well designed in meaning of following.
I have to execute one call which return list and then have to iterate and call other end point to get data for item from list and then again in data have new list for which I have to call 3rd API end point.
After I receive all data I combined it to one items array and place it in state in componentDidMount function but this final step works only if I surround it with setTimeout.
Is there some elegant way to do that?
I'm using fetch and really pure react components, have my own simple service from where I call API, here is some code parts...
items[tag].sensors = [];
API.getObjects(sessionData, userDetails, tag).then(links => {
Object.keys(links.link).forEach(link => {
API.getObjects(sessionData, userDetails, link).then(objLink => {
Object.keys(objLink.link).forEach(function (key) {
let obj = objLink.link[key];
if (obj && obj.type === 'sensor') {
API.getSensorNames(sessionData, key).then(response => {
const sensor = response.sensor;
// some sensor calculations....
items[tag].sensors.push(sensor);
});
}
});
});
});
});
// this part only works if it's surrounded with timeout
setTimeout(function() {
let processedItems = [];
for (var key in items) {
if (items.hasOwnProperty(key)) {
processedItems.push(items[key]);
}
}
self.setState({
items: processedItems
});
}, 1000);
Thanks in advance.
Simply, You can use Promise to wait until you get values from the API call, therefore you will put your code in function like this
function prepareItems() {
items[tag].sensors = [];
return new Promise((resolve, reject) => {
API.getObjects(sessionData, userDetails, tag).then(links => {
Object.keys(links.link).forEach(link => {
API.getObjects(sessionData, userDetails, link).then(objLink => {
Object.keys(objLink.link).forEach(function(key) {
let obj = objLink.link[key];
if (obj && obj.type === "sensor") {
API.getSensorNames(sessionData, key).then(response => {
const sensor = response.sensor;
// some sensor calculations....
items[tag].sensors.push(sensor);
// whenever you set resolve it will end the promise
//and pass the result it to the then function
resolve(items)
});
}
});
});
});
});
});
}
and use then to get the result from the prepareItems function after its resolved
prepareItems().then(items => {
//Do what ever you want with prepared item
})
What about using async/await operators.
These operators allows you to wait until the response is ready.
You can use this kind of helper function.
getItems = async (...) => {
...
items[tag].sensors = []
const links = await API.getObjects(sessionData, userDetails, tag)
Object.keys(links.link).forEach(async (link) => {
const objLink = await API.getObjects(sessionData, userDetails, link)
Object.keys(objLink.link).forEach(async (key) => {
let obj = objLink.link[key]
if (obj && obj.type === 'sensor') {
const response = await API.getSensorNames(sessionData, key)
const sensor = response.sensor
items[tag].sensors.push(sensor)
}
})
})
this.setState({ items })
}
Also you can see this great documentation.

Categories