I can only give an example of my code, since my real code is too big. I'm working with Firebase Cloud Functions and Firebase Firestore.
First I define a returnData dictionary (within my Firebase Cloud Function):
let returnData = {};
Then I get() "snapshots" of my db:
const db = admin.firestore();
const snapshotUsers = await db
.collection("users")
.where("followers", "array-contains", exampleId)
.get();
Then, I'm trying to populate the returnData dictionary:
snapshotUsers.forEach(async (docUsers) => {
await db
.collection("users")
.doc(docUsers.id)
.collection("profileProjects")
.listDocuments()
.then(async (documentRefsProjects) => {
if (!(Object.keys(documentRefsProjects).length === 0)) {
return await db.getAll(...documentRefsProjects);
}
})
.then(async (documentSnapshotsProjects) => {
if (!!documentSnapshotsProject) {
for (let documentSnapshotProject of documentSnapshotsProjects) {
if (documentSnapshotProject.exists) {
// ----------- Populate here -----------
returnData = {
...returnData,
[documentSnapshotProject.id]: {
followers: docUsers.data().followers,
}
};
// --------------------------------------
}
}
}
})
});
// Send response when forEach is done with returnData
res.status(201).send({ returnData: returnData });
Problem is: once it successfully populates returnData, it then throws that data away and
res.status(201).send({ returnData: returnData }); returns the initial empty dictionary
I understand that: "forEach() executes the callback function once for each array element; unlike map() or reduce() it always returns the value undefined and is not chainable", but I seems like I have no other option than to use forEach for firestore, so I'm stuck.
I tried using the map() function instead, but Firestore returns TypeError: snapshotUsers.map is not a function.
I even tried doing a manual for...in and for...of loop, but those also gave me errors when I would try to call id on docUsers:
for (let docUsers of snapshotUsers) {
await db
.collection("users")
.doc(snapshotUsers[docUsers].id) // <---- error'd here
I'm having a real tough time here since the only way I can test this is deploying the Firebase Cloud Function every time.
I wondering if anyone can help me out here. Any advice or solution helps.
I'm having a real tough time here since the only way I can test this is deploying the Firebase Cloud Function every time.
check it: https://firebase.google.com/docs/hosting/test-preview-deploy
I used to work with:
firebase serve --only functions
And it just work fine.
For your problem...
Your foreach is ending before your returnData is populated.
Foreach has one argument that show the current data index in your array.
So, in your success callback, you can check if it is the last array's data then just send your response.
Something like this:
let returnData;
xpto.forEach((data,index)=>{
db.then(()=>{
//populates your returnData
if(index === xpto.lenght-1) res.status(201).send({ returnData })
})
})
Edit with askers experience:
for some reason didn't work with the snapshots, I had to initialize an
index outside the forEach and increment it within the .then()
function.
Related
I was making an app and had to fetch data from Realtime Database. I am getting the data in snapshot.val() like this
►{xz0ezxzrpkb:{…}}
▼xz0ezxzrpkb:{blood_group:"B+",cause:"Random Cause",created_on:"08-02-2022",email:"example#gmail.com",location:"Random Location",message:"Random Message",name:"Any_Name",phone_number:"+91 *********"}
And Now I want to access this data. When I am trying snapshot.val()[0].email and
snapshot.val().[0].email I am Getting
undefined (2)
So, I am working in React Native and this is the code
db.ref('/requests/').on('value', (snapshot) => {
console.log(snapshot.val())
console.log(snapshot.val()[0].email)
console.log(snapshot.val().[0].email)
});
The nodes of database are as follows:
Please help me out.
While the approach in Dharmaraj's answer works, I recommend using Firebase's built-in forEach operation, since that ensures that you process the results in the same order the database returns them:
db.ref('/requests/').on('value', (snapshot) => {
snapshot.forEach((childSnapshot) => {
console.log(childSnapshot.key) // "xz0ezxzrpkb"
console.log(childSnapshot.val()) // {blood_group:"B+",cause:"Random Cause", ...
console.log(childSnapshot.val().email) // "example#gmail.com"
console.log(childSnapshot.child('email').val()) // "example#gmail.com"
})
})
The snapshot value is not an object. Try refactoring the code using Object.keys() as shown below:
db.ref('/requests/').on('value', (snapshot) => {
Object.keys(snapshot.val()).forEach((key) => {
const request = snapshot.val()[key];
console.log(key, request.email)
})
});
I am trying to get data from firebase but it returns empty value when the app loads, but if I edit something on that file even the commented line, then the data loads and app runs, I want when the app opens all data should be there from firebase to run app. and also how to arrange "grabbedData" in reverse order tried grabbedData.reverse() but doent work.
const Getdata = async () => {
let grabbedData = [];
await firebase
.database()
.ref(`/users/`)
.orderByKey()
.on("value", (snapshot, key) => {
// console.log("snapshot....", snapshot);
grabbedData.push(snapshot.val());
});
setUserdata(grabbedData);
console.log("grabbedData", grabbedData); // empty value here :(
if (grabbedData) {
let getfranchiseuid = "";
Object.keys(grabbedData).map(function (key) {
let y = grabbedData[key];
Object.keys(y).map(function (key2) {
let x = y[key2];
if (key2 === uid) {
getfranchiseuid = x.franchiseuid;
}
});
});
if (getfranchiseuid) {
let customerList1 = [];
firebase
.database()
.ref(`/serviceProvider/${getfranchiseuid}/franchise/customers`)
.orderByKey()
.on("value", (snapshot) => {
customerList1.push(snapshot.val());
});
setCustomerList(customerList1);
console.log("customerList1customerList1", customerList1);
}
}
};
useEffect(() => {
var unsubscribe = firebase.auth().onAuthStateChanged(function (user) {
if (user) {
storeUser({ user });
setUser(user);
setEmail(user.email);
setUid(user.uid);
} else {
// No user is signed in.
}
});
unsubscribe();
Getdata();
}, []);
Data is loaded from Firebase asynchronously. Since this may take some time, your main JavaScript code will continue to run, so that the user can continue to use the app while the data is loading. Then when the data is available, your callback is invoked with that data.
What this means in your code is that (for example) right now your setUserdata is called before the grabbedData.push(snapshot.val()) has run, so you're setting any empty user data. You can most easily see this by setting some breakpoints on the code and running it in a debugger, or by adding logging and checking the order of its output.
console.log("1");
await firebase
.database()
.ref(`/users/`)
.orderByKey()
.on("value", (snapshot, key) => {
console.log("2");
});
console.log("3");
When you run this code, the output will be:
1
3
2
This is probably not what you expected, but it is exactly correct and does explain your problems.
The solution for this is always the same: any code that needs the data from the database must be inside the callback, or be called from there.
So for example:
await firebase
.database()
.ref(`/users/`)
.orderByKey()
.on("value", (snapshot, key) => {
grabbedData.push(snapshot.val());
setUserdata(grabbedData);
});
this will ensure that setUserdata is called whenever you updated the grabbedData.
Since you have much more code that depends on grabbedData, that will also have to be inside the callback. So the entire if (grabbedData) { block will need to be moved, and probably others. If you keep applying the solution above, the code will start working.
This is a very common problem for developers that are new to calling asynchronous cloud APIs, so I highly recommend reading some of these other answers:
Why Does Firebase Lose Reference outside the once() Function?
Best way to retrieve Firebase data and return it, or an alternative way
How do I return the response from an asynchronous call? (this one is not specific to Firebase, as the problem is not specific to Firebase)
I am using Firebase authentication to store users. I have two types of users: Manager and Employee. I am storing the manager's UID in Firestore employee along with the employee's UID. The structure is shown below.
Firestore structure
Company
|
> Document's ID
|
> mng_uid: Manager's UID
> emp_uid: Employee's UID
Now I want to perform a query like "Retrieve employees' info which is under the specific manager." To do that I tried to run the below code.
module.exports = {
get_users: async (mng_uid, emp_uid) => {
return await db.collection("Company").where("manager_uid", "==", mng_uid).get().then(snaps => {
if (!snaps.empty) {
let resp = {};
let i = 0;
snaps.forEach(async (snap) => {
resp[i] = await admin.auth().getUser(emp_uid).then(userRecord => {
return userRecord;
}).catch(err => {
return err;
});
i++;
});
return resp;
}
else return "Oops! Not found.";
}).catch(() => {
return "Error in retrieving employees.";
});
}
}
Above code returns {}. I tried to debug by returning data from specific lines. I got to know that the issue is in retrieving the user's info using firebase auth function which I used in forEach loop. But it is not returning any error.
Thank you.
There are several points to be corrected in your code:
You use async/await with then() which is not recommended. Only use one of these approaches.
If I understand correctly your goal ("Retrieve employees' info which is under the specific manager"), you do not need to pass a emp_uid parameter to your function, but for each snap you need to read the value of the emp_uid field with snap.data().emp_uid
Finally, you need to use Promise.all() to execute all the asynchronous getUser() method calls in parallel.
So the following should do the trick:
module.exports = {
get_users: async (mng_uid) => {
try {
const snaps = await db
.collection('Company')
.where('manager_uid', '==', mng_uid)
.get();
if (!snaps.empty) {
const promises = [];
snaps.forEach(snap => {
promises.push(admin.auth().getUser(snap.data().emp_uid));
});
return Promise.all(promises); //This will return an Array of UserRecords
} else return 'Oops! Not found.';
} catch (error) {
//...
}
},
};
Where is my call wrong? The first console.log leads to the role object and the second console.log leads to undefined. When it should be the user.
componentDidMount(){
let user = fire.auth().currentUser;
let db = fire.database();
let roleRef = db.ref('/roles');
roleRef.orderByChild('user').equalTo(user.uid).once('value', (snapshot) => {
console.log(snapshot.val())
console.log(snapshot.val().user);
})
}
Result:
Firebase:
When you execute a query against the Firebase Database, there will potentially be multiple results. So the snapshot contains a list of those results. Even if there is only a single result, the snapshot will contain a list of one result.
Your code doesn't take the list into account. The easiest way to do so is with Snapshot.forEach():
roleRef.orderByChild('user').equalTo(user.uid).once('value', (snapshot) => {
snapshof.forEach((roleSnapshot) => {
console.log(roleSnapshot.val())
console.log(roleSnapshot.val().user);
});
})
Shown above is my firestore collection.
I am attempting to get data from this collection using a Google Cloud Function that I have deployed:
const admin = require('firebase-admin')
const functions = require('firebase-functions')
module.exports= function(request, response){
let results = []
admin.firestore().collection('news_stories')
.get()
.then(docs => docs.map(doc => results.push(doc.data())))
.catch(e => resoponse.status(400).send(e))
response.status(200).send(results)
}
When I run the above function I get an:
Error: could not handle the request
I also tried running the function this way to see if it would work.
module.exports= function(request, response){
let ref = admin.firestore().collection('news_stories')
.get()
.then(docs => response.status(200).send(docs))
.catch(e => resoponse.status(400).send(e))
}
This function returned a this JSON object:
There is no information regarding data or any of the docs.
I uploaded the collection to the firestore DB using this function:
const functions = require('firebase-functions')
const admin = require('firebase-admin')
module.exports = function(request,response){
if(!request.body.data){
response.status(422).send({error: 'missing data'})
}
let data = request.body.data
data.map(item => {
admin.firestore().collection('news_stories').add({item})
})
response.status(200).send('success!')
}
Not sure what I am doing wrong. Why is the function not returning any of the documents?
Data is retrieved from Firestore asynchronously. By the time your send you response back to the caller, the results haven't been retrieved from Firestore yet.
It's easiest to see this by replacing the bulk of the code with three log statements:
console.log("Before starting get");
admin.firestore().collection('news_stories')
.get()
.then(() => {
console.log("In then()");
});
console.log("After starting get");
It's best if you run the above in a regular node.js command, instead of in the Cloud Functions environment, since the latter may actually kill the command before the data is loaded.
The output of the above is:
Before starting get
After starting get
In then()
That is probably not the order that you expected. But because the data is loaded from Firestore asynchronously, the code after the callback function is allowed to continue straight away. Then when the data comes back from Firestore, your callback is invoked and can use the data as it needs to.
The solution is to move all the code that requires the data into the then() handler:
const admin = require('firebase-admin')
const functions = require('firebase-functions')
module.exports= function(request, response){
admin.firestore().collection('news_stories')
.get()
.then(docs => {
let results = []
docs.map(doc => results.push(doc.data()))
response.status(200).send(results)
})
.catch(e => resoponse.status(400).send(e))
}
So after some trouble shooting I found the source of the problem . For some reason if you use .map on the return object the server will respond with a 500 status...
change the .map to forEach and the function works
this will work ...
admin.firestore().collection('news_stories')
.get()
.then(docs => {
let data = []
docs.forEach(doc => data.push(doc.data()))
response.status(200).send(data)
})
yet this wont ...
admin.firestore().collection('news_stories')
.get()
.then(docs => {
let data = []
docs.map(doc => data.push(doc.data()))
response.status(200).send(data)
})