Firestore snapshot not updating properly - javascript

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) => { ... })
}

Related

Document in nested collection is immediately removed after reappearing adding it to the collection

I'm using firestore and react to build my app.
This is how I add a document to the nested collection:
const handleWatch = () => {
if(watchedList.indexOf(foundProfile.id) > -1) {
alert('User already in list');
} else {
db.collection("profiles")
.doc(myProfile.id)
.collection("watched")
.add({
watchedUserId: foundProfile.id
})
.then(success => props.onWatchProfile())
}
}
And this is how I remove it:
const handleUnwatch = (id) => {
db.collection("profiles")
.doc(myProfile.id)
.collection("watched")
.where("watchedUserId", "==", id)
.onSnapshot(snapshot => snapshot.forEach(it => it.ref.delete()));
}
and I'm testing the following path:
Add a user with nick X
Delete user with nick X
Add a user with nick X
Delete user with nick X
Points 1 and 2 work perfectly.
During point 3 the user is added to the nested collection but immediately is deleted. In the firestore console, I see a blinking document.
What is the reason? Maybe it is problem because of firestore's cache?
I didn't test your code but I think this is because you don't detach the listener set in handleUnwatch. Therefore the listener is immediately triggered when you create a new doc and consequently deletes this doc (.onSnapshot(snapshot => snapshot.forEach(it => it.ref.delete()));).
The listener can be cancelled by calling the function that is returned when onSnapshot is called (doc). Depending on your exact workflow you need to call this function when necessary.
For example:
const handleUnwatch = (id) => {
const unsubscribe = db.collection("profiles")
.doc(myProfile.id)
.collection("watched")
.where("watchedUserId", "==", id)
.onSnapshot(snapshot => {
snapshot.forEach(it => it.ref.delete());
unsubscribe();
);
}
Again, the exact place where you call unsubscribe() depends on your exact functional requirements.

Create a firestore doc for each auth user in Nextjs (only using sign in with Google)

I am building a user auth system with Nextjs
I am trying to create a document within firestore for each user in my firebase authentication system. I was easily able to do this in previous projects when creating an account with email and password but with the 'sign in with google' feature I can't seem to figure out how.
I don't want to create a new document every time the user logs in..
My only idea is this:
When user signs in, loop through all firestore documents and see if the users e-mail matches any firestore doc email. If not, create document, else return.
I feel like there is another way though..
Simplest way would be to make a custom hook that can be used anywhere across the application.
First in the _app file inside useeffect hook simply try to get the data from doc if data exist well it means user document is already there and if data does not exists, we need to create a document for that, quite simple. Let's see the code now
Make sure you read comments written inside the code to better understand
In _app.js,
useEffect(async () => {
// now this checks if user is logged in or not
const unsubscribe = auth.onAuthStateChanged(async (userAuth) => {
if (userAuth) {
// if logged in it simply passes the userAuth object to handle user profile
// which is a custom hook to check if document for this user pre-exist or not!
// if there wont be any document it will go and create a document and return
// that document.
// If already there is a document created it will simply return that.
const userRef = await handleUserProfile(userAuth);
userRef.onSnapshot((snapshot) => {
// later you can save currentUsr value in any of the state to use it later
const currentUsr = {
id: snapshot.id,
...snapshot.data(),
};
}
});
}
}
});
return () => unsubscribe();
}, []);
Now the custom hook to check if document is already there or not, here comes the tricky part.
export const handleUserProfile = async (userAuth) => {
// as a second check it check if falsy values are returned
if (!userAuth) return;
const { uid } = userAuth;
// first it tries to get data from that uid
const userRef = firestore.doc(`users/${uid}`);
const snapshot = await userRef.get();
// checks if snapshot exist
if (!snapshot.exists) {
// if snapshot does not exist, it will simply create a document with document
// name as this 'uid'
const { displayName, email } = userAuth;
const timeStamp = new Date();
try {
// making use of same userRef that we created above to create
await userRef.set({
displayName,
email,
createdAt: timeStamp,
});
} catch (error) {}
}
// if snapshot exist it will simply return the userRef which contains the
// document only.
return userRef;
};
Voila! :)
There is no reason why you should not use the onAuthStateChanged event on auth. A write would cost you the same as a read to check if the data is already there. But with a read you would sometimes need also a write. In total only writes every time come less expensive in read/write actions.
Just listen to auth state changes and update your firestore data each time it changes:
firebase.auth().onAuthStateChanged(async (user) => {
if (user) {
await firebase.firestore()
.collection("users")
.doc(user.uid)
.set(data, {merge:true});
// User is signed in.
}
});
Make sure to use set with merge turned on. That will ensure that the data will be created if it doens't exist and update only the field you want to update.
Also make sure to store the data under the user uid. With that you ensure that each user has an unique idenfier. It is a bad practice to store users under the email. One of the reasons for that is that emails could have chars that are not supported as keys so would need to remove those when saving and add them again when reading the keys.
Firestore won't create duplicate docs if created when signing in with Google.. so this works:
const signInWithGoogle = () => {
fire
.auth()
.signInWithPopup(google_provider)
.then((result) => {
/** #type {firebase.auth.OAuthCredential} */
var credential = result.credential;
// This gives you a Google Access Token. You can use it to access the Google API.
var token = credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
})
.catch((error) => {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
// ...
})
// CREATE USER DATA IN FIRESTORE
.then(async () => {
const data = {
//ADD DATA HERE
};
await fire
.firestore()
.collection("users")
.doc(fire.auth().currentUser.email)
.set(data);
});
};

Not getting data from firebase on opening the app

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)

auth.onAuthStateChanged not triggering after auth.currentUser.updateProfile in Firebase

I have a component updating the user's profile pic:
const Updater = () => {
const updateProfilePic = async (photoURL) => {
await auth.currentUser.updateProfile({ 'photoURL': photoURL });
}
}
I have a second component detecting changes in the user's state:
const StateChangesDetector = () => {
auth.onAuthStateChanged( user => {
if(user)
console.log('User changed state', JSON.stringify(user));
});
}
The problem is that auth.onAuthStateChanged() is not triggering after the execution of updateProfile(). Thus, I'm getting the old user's state and the old profile picture.
How can I force to trigger auth.onAuthStateChanged() after updating the user's profile picture?
Try the reload() function on the currentUser object to get updated information. Note that it's asynchronous and returns a promise. If it doesn't trigger the listener (as it's not really a "state" change, just a refresh of data), I suspect you might have to access the firebase.auth().currentUser again after the returned promise resolves to see new data.

Set on firebase and then set firebase claims

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
});

Categories

Resources