I have a firestore collection containing post documents, each document contains a reference to an author (user) and a case document.
How do I get the user and the case in the same onSnapshot?
Here's what I'd like to do with await, but that doesn't seem to be an option with react-native-firebase.
export const firebasePostLooper = (snapshot) => {
let data = [];
snapshot.forEach(async (doc) => {
let newItem = {id: doc.id, ...doc.data()};
if (newItem.author) {
let authorData = await getDoc(newItem.author); // doesn't work with rnfirebase
if (authorData.exists()) {
newItem.userData = {userID: authorData.id, ...authorData.data()};
}
}
if (newItem.case) {
let caseData = await getDoc(newItem.case);
if (caseData.exists()) {
newItem.userData = {userID: caseData.id, ...caseData.data()};
}
}
data.push(newItem);
});
return data;
};
This doesn't work because getDoc() doesn't exist.
So I'm left with using .then()
export const firebasePostLooper = (snapshot) => {
let data = [];
snapshot.forEach((doc) => {
let newItem = {id: doc.id, ...doc.data()};
if (newItem.author) {
newItem.author
.get()
.then((res) => {
newItem.authorData = res.data();
if (newItem.case) {
newItem.case
.get()
.then((caseRes) => {
newItem.caseData = caseRes.data();
data.push(newItem);
})
.catch((err) => console.error(err));
}
})
.catch((err) => console.error(err));
} else {
data.push(newItem);
}
});
return data;
};
This second method doesn't seem to be working, data is empty at the return statement but data.push(newItem) contains the correct document with the 2 referenced documents.
You're returning data before it gets filled inside the promise. You should handle the returning of the data inside a .then() in order to return it after the promise has resolved and not before.
Take a look at this example where if we handle the emptyData object outside the promise chain, we just return the initial value before it has been filled.
let promise = new Promise((resolve, reject)=>{
setTimeout(resolve, 1000, 'foo');
})
let emptyData= [];
let notEmptyData = [];
promise
.then(res=>{
emptyData.push(res);
notEmptyData.push(res);
console.log("Full data: " + notEmptyData) // "Full data: foo"
});
console.log("Empty data: " + emptyData); // "Empty data: "
Related
How to query collection of firebase so that it also includes its subcollection? So I have collection posts with documents and every document has field subcollection likes. After getting collection posts I have objects and inside them values of fields, but inside each object (post) I want to have besides those values an object aka subcollection likes.
Queries in Firestore are by definition shallow. There is no way to get results from both a parent collection and its subcollections in a single query.
When I want all data from a parent document and a subcollection in the same operation, and it's not too much, I typically (also) include it in the parent document.
Ive done in it this function but i dont feel like making it look pretty theres tons of code that isnt needed here anyways here is solution maybe it helps someone:
getPosts = async () => {
this.setState({ isLoading: true });
const snapshot = await firebase
.firestore()
.collectionGroup("posts")
.orderBy("id", "desc")
.limit(7)
.get();
let array = [];
const snap = await firebase
.firestore()
.collection("posts")
.get();
for (let i = 0; i < snap.docs.length; i++) {
array.push(snap.docs[i].data().id);
//console.log(array);
//newpost[i].userHandle = [result]; //your new object here
}
if (!snapshot.empty) {
let newpost = [];
this.setState({ lastDoc: snapshot.docs[snapshot.docs.length - 1] });
// snapshot.forEach((doc) => {
// const PostItem = doc.data();
// PostItem.id = doc.id;
// newpost.push(PostItem);
// });
let newpost2 = [];
let result = [];
let finish = [];
let groupByKey = [];
let bruh = [];
let uf = [];
const listOfPosts = await firebase.firestore().collection("posts").get()
.then((val) => val.docs);
for (var i=0; i<listOfPosts.length; i++)
{
const snapshot = await firebase.firestore().collection("posts").doc(
listOfPosts[i].id.toString())
.collection("hugs").get();
snapshot.forEach(doc => {
newpost2.push(doc.data());
result = newpost2.map(a => a.userHandle)
//console.log(newpost2)
newpost2.map(obj=> ({ ...obj, result}))
//console.log(newpost2)
groupByKey.push((list, key, {omitKey=false}) => list.reduce((hash, {[key]:value, ...rest}) => ({...hash, [value]:( hash[value] || [] ).concat(omitKey ? {...rest} : {[key]:value, ...rest})} ), {}))
groupByKey.push((newpost2, 'id', {omitKey:false}))
uf = [];
newpost2.forEach((obj)=> {
if(typeof uf[obj.id] === 'undefined') {
uf[obj.id] = {id: obj.id, userHandle: [obj.userHandle]}
}
else {
uf[obj.id].userHandle.push(obj.userHandle);
}
});
uf = uf.filter(item => !(item == "undefined"));
//console.log(groupByKey)
//this.setState({ post: [...newpost,] });
});
//finish = a1.map(t1 => ({...t1, ...a2.find(t2 => t2.id === t1.id)}))
//console.log(newpost2)
}
for (let i = 0; i < snapshot.docs.length; i++) {
newpost.push(snapshot.docs[i].data());
//newpost[i].username = result;
//newpost[i].userHandle = [result]; //your new object here
}
//var element = {}, cart = [];
//newpost.id = id;
//element.quantity = quantity;
//newpost2.push(newpost);
// Array of Objects in form {element: {id: 10, quantity: 10} }
finish = newpost.map(t1 => ({...t1, ...uf.find(t2 => t2.id === t1.id)}))
//if(Object.keys(groupByKey) === t1.id){console.log("nigba")}
//var result2 = newpost.map(function(e) {
//var find = groupByKey.find(a => a.email == e.email);
//return console.log(Object.assign({}, e, find))
//})
console.log(finish)
this.setState({ post: [...newpost,] });
//console.log(this.state.post)
} else {
this.setState({ lastDoc: null });
}
this.setState({ isLoading: false });
//setTimeout(
//console.log(this.state.post)
//, 50000);
};
I created a firebase function that updates the like count of an comment or post when a new like document is created.
But it throws an 404 error.
exports.updateLikeCount = functions.firestore
.document('likes')
.onCreate((snap, context) => {
const likeObj = snap.data();
if(likeObj.isComment) {
const { _comment } = likeObj;
const commentRef = fstore.collection('comments').doc(_comment);
return commentRef.get()
.then(doc => {
let { likes } = doc.data();
++likes;
return commentRef.update({
likes
});
});
}else {
const { _post } = likeObj;
const postRef = fstore.collection('posts').doc(_post);
return postRef.get()
.then(doc => {
let { likes } = doc.data();
++likes;
return postRef.update({
likes
});
});
}
});
NOTE:
_post and _comment are post ID and comment ID respectively
I want to update document found by a query. Actually I'm trying the following approach:
export const add_user = functions.https.onRequest((request, response) => {
corsHandler(request, response, () => {
const collectionReference = db.collection("users");
const query = collectionReference.where("name", "==", "John");
fbid_query.get()
.then(function(querySnapshot) {
if (querySnapshot.empty) {
console.log("John notfound");
} else {
console.log("John found!");
querySnapshot.forEach(function (documentSnapshot) {
add_update(query, "George");
});
}
response.json([]);
})
.catch(function(error) {
response.json([]);
});
});
});
.
const add_update = function (query, new_name) {
query.get(function(querySnapshot) {
querySnapshot.forEach(function(document) {
console.log("entered in forEach");
document.ref.update({
name: new_name
});
});
}).then(function () {
console.log("success");
})
.catch(function(error) {
console.error("error: ", error);
});
}
The only console.log that appears is success, although nothing is updated and John indeed exists in Cloud Firestore.
What am I doing wrong?
PS: I don't know if it matters but before I actually try to update, I call query.get() just to check if something exists (and after I call that approach to update).
I have the following method I'm accessing when my VueJS component is loading:
getServices () {
fb.servicesCollection.get().then(querySnapshot => {
this.serviceList = []
querySnapshot.forEach(doc => {
const { name, icon } = doc.data()
fb.storage.ref().child(icon).getDownloadURL().then(function (url) {
console.log(url)
})
this.serviceList.push({id: doc.id, name: name, icon: 'iconURL'})
})
this.isLoading = false
}).catch(error => {
console.log(error)
})
}
What I want to achieve is to get the url to replace the current 'iconURL' string. Didn't find any method to do that in the last couple of hours. Please help!
The following should do the trick. (However note that I could no test it, so it may need a bit of fine tuning... You can report how it works in the comments and we correct it if necessary)
Since you want to execute several getDownloadURL() asynchronous calls to Firebase Storage in parallel, you have to use Promise.all(), since getDownloadURL() returns a promise, see the doc.
getServices () {
let namesArray = []
let docIdArray = []
fb.servicesCollection.get().then(querySnapshot => {
this.serviceList = []
let promises = []
querySnapshot.forEach(doc => {
const icon = doc.data().icon;
promises.push(fb.storage.ref().child(icon).getDownloadURL())
namesArray.push(doc.data().name)
docIdArray.push(doc.id)
})
return Promise.all(promises)
})
.then(results => {
results.forEach((value, index) => {
this.serviceList.push({id: docIdArray[index], name: namesArray[index], icon: value})
})
})
}).catch(error => {
console.log(error)
})
}
This is how I got it in the end...
getServices () {
fb.servicesCollection.get().then(querySnapshot => {
this.serviceList = []
querySnapshot.forEach(doc => {
const { name, icon } = doc.data()
fb.storage.ref(icon).getDownloadURL().then(url => {
this.serviceList.push({id: doc.id, name: name, icon: url})
})
})
this.isLoading = false
}).catch(error => {
console.log(error)
})
}
Thank you for all your efforts to help me!!! Highly appreciate it!
I have a Redux action which needs to make 2 subsequent ajax calls.
The first calls googlemaps api: https://maps.googleapis.com/maps/api/geocode/json?address=${searchTerm}&key=${gmapsKey}
The second calls a local service based on those results
/api/content/stores/byDistance/${lat},${lng}/sort
I'm using superagent to do the ajax calls. Clearly I'm experience difficulties keeping track of the promises, and including failures.
Am I mis-undestanding a core concept of Promises? Is there a simpler way to write the below?
export function loadBySearch(searchTerm) {
const geoSearchUrl = `https://maps.googleapis.com/maps/api/geocode/json?address=${searchTerm}&key=${gmapsKey}`;
return {
types: [LOAD, LOAD_BY_LAT_LONG, LOAD_FAIL],
//Do I need to make this promise here?
promise: (client) => {
const promise = new Promise( (resolve, reject) => {
console.info('making google geocode request', geoSearchUrl);
superagent.get(geoSearchUrl)
.set('Accept', 'application/json')
.then( (successData1) =>{
const results = successData1.body.results;
if (!results.length) {
reject(`no results found for this search : ${searchTerm}`);
return;
}
const lat = results[0].geometry.location.lat;
const lng = results[0].geometry.location.lng;
const path = `/api/content/stores/byDistance/${lat},${lng}/sort`;
client.get(path).then(
(successData2) => {
resolve( {
searchTerm: searchTerm,
searchLocation: {
lat,
lng
},
data: successData2
});
},
(errorData2) => {
reject( {
searchTerm: searchTerm,
result: errorData2
});
},
);
},
(errorData1) => {
reject({
searchTerm: searchTerm,
result: errorData1
});
}
);
});
return promise;
}
};
}
I'm not using superagent, but I'm guessing something like this might just work:
superagent.get(geoSearchUrl)
.set('Accept', 'application/json')
.then(successData1 => {
const results = successData1.body.results;
if (!results.length) {
throw(`no results found for this search : ${searchTerm}`);
}
return Promise.resolve(results);
})
.then(results => {
const lat = results[0].geometry.location.lat;
const lng = results[0].geometry.location.lng;
const path = `/api/content/stores/byDistance/${lat},${lng}/sort`;
return client.get(path);
})
.then(successData2 => {
return Promise.resolve({
searchTerm: searchTerm,
searchLocation: {
lat,
lng
},
data: successData2
});
})
.catch(error => {
return Promise.reject({
searchTerm: searchTerm,
result: error
});
});
Haven't test it, but I hope at least it helps ;)