Firebase: using DocumentSnapshot id as User uid. Thoughts? - firebase

Currently users profiles are created by adding a user to a firestore collection.
I then have an onCreate function that will create a user in Firebase Authentication.
Would there be an issue with using the firestore docId as the created users uid?
Thanks
export const createUser = functions.region('europe-west2')
.firestore.document('users/{user}')
.onCreate(async (snap, context) =>
executeOnce(context.eventId, async (transaction: FirebaseFirestore.Transaction) => {
const data = snap.data();
await auth.createUser({
uid: snap.id,
email: data.email,
password: //,
displayName: `${data.firstName} ${data.lastName}`,
}).then(async function (newUser) {
return db.collection('users').doc(snap.id)
.update({
status: 'Active',
});
}).catch(function (error) {
return console.error(`Error creating user, ${error}`);
});
}));

I don't see why that would be an issue. Usually UID from Firebase Authentication is used as Firestore document ID. It's just an identifier so you can accurately point at the document containing current user's information. (<= just an example)
const uid = firebase.auth().currentUser.uid
const docRef = firebase.firestore().collection("users").doc(uid)
At the end, it's just a unique string. So as long as they both are same, you should be fine. Even if they are not, you could still query Firestore document using .where("userId", "==", uid).

Related

In Flutter, using await stores data correctly, but not collection name and not using await does not store data correctly, but correct collection name

My current code uses:
var currentUID = await database.getCurrentUserID();
Running this function with await on this line of code stores data in Firestore with correct user ID but time is always set to 0:
Future<void> addUserTime() async {
var currentUID = await database.getCurrentUserID();
return await database.workoutCollection
.doc(currentUID.toString())
.set({
'Workout Time': format(duration),
})
.then((value) => print('Time added'))
.catchError((error) => print('Failed to add time to database'));
}
Without using await like the previous line of code like this:
var currentUID = database.getCurrentUserID();
Firestore shows this: This is the firebase output. Wrong UserID from Firebase Authentication, but time is always set to what the user logged:
Future<void> addUserTime() async {
var currentUID = database.getCurrentUserID();
return await database.workoutCollection
.doc(currentUID.toString())
.set({
'Workout Time': format(duration),
})
.then((value) => print('Time added'))
.catchError((error) => print('Failed to add time to database'));
}
This is my database class where I call the getCurrentUserID() function:
How can I get both the correct UID and correct time the user logged?
FirebaseAuth stores the current user once it's authenticated in FirebaseAuth.instance.currentUser, if we look into this property we will find that the type is User?, not Future<User?>, hence you don't need to await to get the currentUser, simply:
return FirebaseAuth.instance.currentUser?.uid;
Additionally, currentUser?.uid returns a String, so no need to call .toString().
Assuming that the duration is not 0, with these modifications the code should work, for further reference here's a DartPad example that updates a user record based on currentUser.uid.

Displaying user data from Firebase Firestore in React Native within a Text tag

Background
Hey! I'm trying to build a header for the home page of my app. I have succesfully implemented email/password auth with Firebase and also persistent credentials. However, I am unable to fetch data from the firestore collection which stores my users.
Basically, what I need is to have my header display "Hello {userName}", but I have absolutely no clue on how to do that.
My Home Screen is a function component, not a class component, so as far as I know I can't go the "componentDidMount()" way.
Question
Which is the best way to fetch the current user's data and display a specific field of information, such as their first name?
How would I go about rendering that within a <Text> tag? Is it something like <Text> Hello {this.user.firstName}</Text> or am I absolutely wrong?
What I've tried
I know this has something to do with this line of code: const usersRef = firebase.firestore().collection('users') but I've no idea what to follow it up with. Also have tried with this method:
var user = firebase.auth().currentUser;
var firstName, email, photoUrl, uid, emailVerified;
if (user != null) {
firstName = user.firstName;
email = user.email;
photoUrl = user.photoURL;
emailVerified = user.emailVerified;
uid = user.uid;
}
But that doesn't seem to work. On the last example I'm calling firstName like this: <Text>Hello {firstName}</Text>
You are confusing auth with firestore. Firebase auth only provides credentials and the ability to login and does not enter any data into a database (firestore). What you want to do is when a user is registering you want to set a corresponding document in firestore with the users uid (identification) and all of the users custom data (First name, etc).
Here is an example:
const onRegister = async () => {
try {
const credential = await auth.createUserWithEmailAndPassword(
'email#email.com',
'password',
);
const {uid} = credential;
// your data here (dont forget to store the uid on the document)
const user = {
firstName: 'whatever',
lastName: 'whatever',
user_id: uid,
};
await firestore().collection('users').doc(uid).set(user);
} catch {
//do whatever
}
};
and then when you wanna get their data you can access their document and display it:
const [user, setUser] = useState();
const {uid} = auth().currentUser;
const getUser = async () => {
try {
const documentSnapshot = await firestore()
.collection('users')
.doc(uid)
.get();
const userData = documentSnapshot.data();
setUser(userData);
} catch {
//do whatever
}
};
// Get user on mount
useEffect(() => {
getUser();
}, []);
// return info if user is set
return (
<Text>{user && user?.firstName}</Text>
);

Firebase Cloud Function not firing

I'm trying to run the following Cloud Function:
exports.getUserData = functions.firestore
.document('UserData/{id}')
.onWrite(async (snap, context) => {
const uid = snap.data.id;
let uData;
console.log("onCreate called. uid="+uid);
await admin.auth().getUser(uid)
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully fetched user data:', userRecord.toJSON());
uData = userRecord.toJSON();
})
.catch(function(error) {
console.log('Error fetching user data:', error);
});
await admin
.firestore()
.doc('UserData/${uid}')
.set({
userRecord : uData
});
return null;
});
It gets deployed allright, as I can see it in the console. But adding/updating a doc in the collection simply does not trigger the function (nothing shows in log).
A couple of things, as I see a few problems
Seems to me that you want to trigger this function every time there is a new UserData collection. If this is the case, you should use the trigger onCreate. onWrite gets triggered every time a doc is updated, created or deleted.
You function is creating an infinite loop if you use onWrite. You are updating collections which will triggered the same function, over and over.
First argument of the function is not a snapDoc, if you are using onWrite. Check the documentation
This part:
await admin
.firestore()
.doc('UserData/${uid}')
.set({
userRecord : uData
});
'UserData/${uid}' is a string not a template string. Use backtick ` not single quote '
As #renaud-tarnec said, use context.params to get the id parameter
It seems that by doing
exports.getUserData = functions.firestore
.document('UserData/{id}')
.onWrite(async (snap, context) => {
const uid = snap.data.id;
//...
});
you want to assign to the uid variable the value of the {id} wildcard in the 'UserData/{id}'.
For that you should use the context Object, as follows:
const uid = context.params.id;
and as explained here in the doc.

Using vuex, firestore, and createUserWithEmailAndPassword, how do I create a user profile collection when a user registers?

For the app I'm building, I want my users to have a profile created for them when they register; the profile would contain the user's username, email, and the uid created by firebase authentication. I've got the authentication portion using createUserWithEmailAndPassword to work on its own. I'm also able to create a "users" collection, capturing a username and the user's email, on its own as well. However, I'm running into trouble grabbing and saving the uid to the user's profile in the user's collection.
Here is the code I have at the moment:
import * as firebase from "firebase/app";
import db from "../../components/firebase/firebaseInit";
actions: {
registerUser({ commit }, payload) {
commit("setLoading", true);
commit("clearError");
firebase
.auth()
.createUserWithEmailAndPassword(payload.email, payload.password)
.then(user => {
commit("setLoading", false);
const newUser = {
email: user.email,
id: user.uid,
courses: []
};
commit("setUser", newUser);
db.collection("users")
.add({
username: payload.username,
email: user.email,
userId: user.uid
})
.then(() => {
console.log("New user added!");
})
.catch(err => {
console.log(err);
});
})
.catch(err => {
commit("setLoading", false);
commit("setError", err);
});
},
In the research I've done, I've found these suggested solutions:
Get Current User Login User Information in Profile Page - Firebase and Vuejs
Cloud Firestore saving additional user data
And this video:
https://www.youtube.com/watch?v=qWy9ylc3f9U
And I have tried using the set() method instead of add(), as well.
But none of them seem to work, for me at least.
Thank you in advance for your help.
And if you need to see any more code, just let me know.
You haven't shared the error message you get, but most probably the error comes from the fact that the createUserWithEmailAndPassword() method returns a UserCredential and not a User.
So you have to do as follows:
import * as firebase from "firebase/app";
import db from "../../components/firebase/firebaseInit";
actions: {
registerUser({ commit }, payload) {
commit("setLoading", true);
commit("clearError");
firebase
.auth()
.createUserWithEmailAndPassword(payload.email, payload.password)
.then(userCredential=> {
commit("setLoading", false);
const user = userCredential.user; // <-- Here is the main change
const newUser = {
email: user.email,
id: user.uid,
courses: []
};
commit("setUser", newUser);
return db.collection("users")
.add({
username: payload.username,
email: user.email,
userId: user.uid
});
})
.then(() => {
console.log("New user added!");
})
.catch(err => {
commit("setLoading", false);
commit("setError", err);
});
},

Reference.set failed: First argument contains undefined

I have created a firebase function that listen on onCreate event, however the DocumentSnapshot.data() is returning empty.
The function code is:
exports.createClientAccount = functions.firestore
.document('/userProfile/{userId}/clientList/{clientId}')
.onCreate(async (snap, context) => {
console.log('****snap.data(): ', snap.data()); //Showing Empty from the console.
return admin
.auth()
.createUser({
uid: context.params.clientId,
email: snap.data().email,
password: '123456789',
displayName: snap.data().fullName,
})
.then(userRecord => {
return admin
.database()
.ref(`/userProfile/${userRecord.uid}`)
.set({
fullName: userRecord.displayName, //ERROR here: Reference.set failed: First argument contains undefined
email: userRecord.email,
coachId: context.params.userId,
admin: false,
startingWeight: snap.data().startingWeight,
});
})
.catch(error => {
console.error('****Error creating new user',error);
});
});
The document IS created on the firebase database under
/userProfile/{userId}/clientList/{clientId}
clientId document created on the database
As per the documentation, onCreate listens when a new document is created and returns the snapshot of the data created through the DocumentSnapshot interface. However, I have checked from the console that snap.data() is empty. I don't understand why it is empty if the document is created successfully on the database.
image showing error returned by the functions when creating the userProfile
From the function code, return admin.auth.createUser({}) is creating the user as anonymous because snap.data().email is undefined, but it should create a non anonymous user.
First, please try to change document('/userProfile/{userId}/clientList/{clientId}') to document('userProfile/{userId}/clientList/{clientId}').
path should not start with /.
exports.createClientAccount = functions.firestore
.document('userProfile/{userId}/clientList/{clientId}')
At the end problem was that when I created the document with add({}) I was not including the fields in the instruction. This is the function that creates the client document and now the function gets triggered correctly.
async clientCreate(
fullName: string,
email: string,
startingWeight: number
): Promise<any> {
const newClientRef = await this.firestore
.collection(`userProfile/${this.userId}/clientList/`)
.add({
fullName,
email,
startingWeight: startingWeight * 1,
});
return newClientRef.update({
id: newClientRef.id
});
}
I was calling this.firestore.collection(...).add({}) with no fields, so when it happened, the cloud function got triggered and the DocumentSnapshot.data() was empty thus returning the Reference.set error. The cloud function createClientAccount is correct. Thanks.

Resources