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)
Related
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.
I have a dating-like app and I want to be able to query for new matches every time a user clicks on the "connection" tab button.
I am not sure if I am writing the await or async incorrectly but if the user moves too fast for the database to return the results, the returned matches are not loaded fast enough. What I have so far is: on the load of the page I callout to Firebase, when the user navigates away and then navigates back to the "connection" tab, I call back out to Firebase. the getMatches() method is the callout to firebase.
const MatchesScreen = ({navigation}) => {
const {state, updateDislikedQueue, updateLikedQueue, getMatches} = useContext(AuthContext);
const [loaded, setLoaded] = useState(false);
const [queue, setQueue] = useState({});
const [noMatches, setNoMatches] = useState(false);
const [updateProfileAndPreferences,setUpdateProfileAndPreferences] = useState(false);
const getMatchesMethod = async () => {
getMatches().then(matches => {
if (!matches) {
Alert.alert("Update Preferences and Profile before connecting");
setUpdateProfileAndPreferences(true);
} else {
setUpdateProfileAndPreferences(false);
let cardData = [];
for (m in matches) {
if (matches[m].id == state.id) {
continue;
} else {
let user = {
id: matches[m].id,
fullName: matches[m].info.fullName
};
cardData.push(user);
}
}
if (cardData.length > 0) {
setQueue(cardData);
setLoaded(true);
} else {
setNoMatches(true);
Alert.alert("No Connections Available");
}
}
});
};
useEffect(() => {
getMatchesMethod();
const unsubcribe = navigation.addListener("willFocus", () => {
getMatchesMethod();
});
// return unsubcribe.remove();
}, []);
Also when I try to unsubscribe, the listener doesn't appear to work when the user navigates back and forth. Any help on what I am doing wrong with the async calls and the listener would be greatly appreciated.
I think you just forgot the await keyword in your function
Happens to me all the time ahaha
I recommend using the await keyword in front of getMatches() instead of the .then() syntax. It makes the code read more synchronously, and it can help prevent errors. Await must always be called from an asynchronous function. It cannot be called from a non async function.
I think you will also need to use an await in front of getMatchesMethod();
Checkout this article for help with calling asynchronous code inside an useEffect()
https://medium.com/javascript-in-plain-english/how-to-use-async-function-in-react-hook-useeffect-typescript-js-6204a788a435
Ex:
const unsubcribe = navigation.addListener("willFocus", async () => {
await getMatchesMethod();
});
I found the issue. I was on V3 of react-navigation which is severely outdated was was conflicting with my react-native and expo versions.
I am in the process of updating everything (RN > .60, EXPO sdk > 35, #react-navigation V5) and will be able to use the listeners for #react-navigation v5 on my getMatches() method.
So i working with firebase auth and database in order to set new user to data base, if set successful i want to set claims for that user.
So it means i have a promise within a promise:
function setUser(user){
// no need for the database code before this, but userRef is set properly
return userRef.set(user)
.then(succ => {
return firebase.firebase.auth().setCustomUserClaims(user.key, {admin: true})
.then(() => {
console.log("setting claims")
return true;
});
})
.catch(err => {
return err
})
}
calling function:
app.post("/register_user",jsonParser,async (req, res) => {
var user = req.body.user;
let result = await fireBase.setUser(user);
res.send(result);
})
What happens is that i get the set on the database but claims are not set nor i can i see the log. I know its a js question and not firebase one. I tried many different ways (with await) but non worked.
firebase.firebase does not seem correct. You need to be using the admin object which can be initialised using const admin = require('firebase-admin'); This is not part of the firebase db sdk, but the admin one. You can also use the userRef.uid as that gives you the id of the document of the user, if that is what you want, else use your user.key
return admin.auth().setCustomUserClaims(userRef.uid, {
admin: true
}).then(() => {
//on success
});
I was wondering if a Firestore document snapshot is being refreshed if the function runs again.
Here I am trying to get a readout of the 'active' property which returns a boolean.
Unfortunately, if I manually change the value in the document to 'false', the readout still shows 'true', even after reloading (and supposedly re-running the function).
// Check if user is logged in
auth.onAuthStateChanged(user => {
if (user) {
findSubscription(user);
} else {
console.log('logged out')
}
})
const findSubscription = (user) => {
// Get document
db.collection('stripe_customers')
.doc(user.uid)
.collection('subscription_info')
.doc('subscription_object')
.get()
.then((doc) => {
// Get number of keys in document
const numberOfKeys = Object.keys(doc.data()).length;
// Check if numverOfKeys is > 0 (it is not if the user has never had a subscription before)
if (numberOfKeys !== 0) {
// Get subscription status
const subscriptionStatus = doc.data().subscription.plan.active;
console.log(subscriptionStatus);
Occasionally I am getting the following console log.
"XMLHttpRequest cannot load ('google api url') due to access control checks."
My database is still in public mode for testing so I am not quite sure what that is all about.
You need to call onSnapshot() instead of get() (https://firebase.google.com/docs/firestore/query-data/listen)
let unsubscribe = null;
const addSubscription = (user) => {
// Subscribe to document updates
// (called immediately & every subsequent change)
unsubscribe = db.collection('stripe_customers')
.doc(user.uid)
.collection('subscription_info')
.doc('subscription_object')
.onSnapshot((doc) => { ... })
}
I need to utilize a key stored in my firebase using the user's firebase user id.
The steps to my function are the following:
1) Get the Firebase User ID after authentication
2) Using the Firebase User ID, pull the stored API Key value (which I saved in a node: app/{Firebase User Id})
3) Using the stored API Key value, run my last function
After doing some research, I've come to the conclusion that I should use Javascript Promise Chaining to the below code, which I'm having a difficult time doing
firebase.initializeApp({databaseURL: "{}"});
var dbRef = firebase.database();
function pull_api_key_from_firebase_after_auth(func){
firebase.auth().onAuthStateChanged((user) => {
if (user) {
var app_ref = dbRef.ref('app').child(user.uid);
app_ref.on("child_added", function(snap) {
dictionary_object = snap.val()
api_key = dictionary_object.api_key
func(api_key)
})
}
});
}
function final_function(api_key){
console.log('processing final function')
}
pull_api_key_from_firebase_after_auth(final_function)
Alternatively, I'd like to make api_key a global variable as such:
function pull_api_key_from_firebase_after_auth(func){
firebase.auth().onAuthStateChanged((user) => {
if (user) {
var app_ref = dbRef.ref('app').child(user.uid);
app_ref.on("child_added", function(snap) {
dictionary_object = snap.val()
api_key = dictionary_object.api_key
localStorage.setItem('api_key',api_key)
})
}
});
}
api_key = localStorage.getItem('api_key')
final_function(api_key)
However I cant figure out how to make final_function as well as my other functions wait until api_key is defined
You must use firebase.auth().onAuthStateChanged to get the uid bcs .auth.currentUser.uid will NOT be available on page load. It shows up a microsecond after (it's asynchronous).
To do what you want simply build out a promise function and call it within .onAuthStateChanged and .then do your other function. You custom promise might look like:
function fetchUserData(){
return new Promise(function(resolve, reject){
var db = firebase.firestore(); // OR realtime db
db.collection("users").get().then(function(snap) {
resolve (snap);
}).catch(function(error) {
reject (error);
});
});
}
Within .onAuthStateChange just do something like:
fetchUserData().then((snap) => {
// do your thing
})
.catch((error) => {
console.log(error);
});