On Creation of an account I need to make 2 collections 1 for Users 1 for Companies.
Within each one, I need to capture the UID. I just cant seem to grab it. I keep getting undefined when console.log it.
component
const handleSubmit = async (e) => {
e.preventDefault()
setError('')
try {
await createUser(email, password).then(() => {
if (false) throw Error('Error')
//NEED Get UserID
addDoc(collection(db, 'Companies'), {
name: company,
//owner: userID,
users: [],
assets: []
}).then((docRef) => {
let companyID = docRef.id
addDoc(collection(db, 'Users'), {
name: name,
email: email,
company: [company],
companyID: [companyID]
//UserID: UserID
})
})
})
authContext
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState({})
const createUser = (email, password) => {
return createUserWithEmailAndPassword(auth, email, password)
}
I have tried everything in the firebase documentation but I believe since I am using context it may process data a bit differently.
The createUser() function is returning a UserCredential object. You can read the user's UID from it. Try refactoring the code as shown below:
const handleSubmit = async (e) => {
e.preventDefault()
setError('')
const { user } = await createUser(email, password)
const docRef = await addDoc(collection(db, 'Companies'), {
name: company,
owner: user.uid,
users: [],
assets: []
})
await addDoc(collection(db, 'Users'), {
name: name,
email: email,
company: [company],
companyID: [docRef.id]
UserID: user.uid
})
}
Related
I'm trying to add new fields in a document that related to the logged in user (same UID)
to create a new document with the same UID as the user's UID I use the following:
const collectionRef = collection(db, 'data');
// submit function to add data
const handleSubmit = async (e) => {
e.preventDefault();
onAuthStateChanged(auth, async (user) => {
const uid = user.uid;
try {
// overrides the data
await setDoc(doc(collectionRef, uid), {
food: food,
quantity: quantity,
})
alert("Data added successfully")
} catch (error) {
alert(`Data was not added, ${error.message}`)
}
}, [])
}
For add more fields to the array, I use the following:
const collectionRef = collection(db, 'data');
// submit function to add data
const handleSubmit = async (e) => {
e.preventDefault();
onAuthStateChanged(auth, async (user) => {
const uid = user.uid;
try {
await updateDoc(doc(collectionRef, uid), {
food: arrayUnion(food),
quantity: arrayUnion(quantity),
})
alert("Data added successfully")
} catch (error) {
alert(`Data was now added, ${error.message}`)
}
}, [])
}
the complete code:
const collectionRef = collection(db, 'data');
const handleSubmit = async (e) => {
e.preventDefault();
onAuthStateChanged(auth, async (user) => {
const uid = user.uid;
try {
// overrides the data - ???
await setDoc(doc(collectionRef, uid), {
food: food,
quantity: quantity,
})
// add more fields to the array
await updateDoc(doc(collectionRef, uid), {
food: arrayUnion(food),
quantity: arrayUnion(quantity),
})
alert("Data added successfully")
} catch (error) {
alert(`Data was now added, ${error.message}`)
}
}, [])
}
I can add more fields with the updateDoc() function, but if I will not comment or delete the setDoc() function after creating the new document, it will override the fields inside the document.
screenshot of the DB (Firestore):
I can add more fields only if I comment the setDoc() function after the doc first created
Firestore
If you want to create-or-update the document, you can use { merge: true }:
await setDoc(doc(collectionRef, uid), {
food: food,
quantity: quantity,
}, { merge: true }) // 👈
Also see the second code snippet in the documentation on setting a documents.
The onAuthStateChanged() observer runs every time the user's auth state changes and then the set() will overwrite the existing document removing any existing data. You can try using merge option that won't delete rest of the fields as shown below:
const docRef = doc(db, 'users', user.uid)
await setDoc(docRef, { food, quantity }, { merge: true })
This will still however overwrite the 'food' and 'quantity' arrays with the new value that you provide. So it might be best to use setDoc() only once after users registers in your application and use updateDoc thereafter.
Alternatively, you can also unsubscribe from the listener as shown below:
const handleSubmit = async (e) => {
e.preventDefault();
const unsub = onAuthStateChanged(auth, async (user) => {
// process data
unsub();
})
}
The handler might still trigger again if auth state changes before your processing is complete. You should use onAuthStateChanged() when the web app loads for the first time and then redirect user to different pages based on their auth state. Once you confirm that user is logged in then you can simply use the following to get current user:
const user = auth.currentUser;
if (user) {
// update document.
}
I have a custom hook:
import React, { useState, useEffect } from 'react';
import { Alert } from 'react-native';
import firebase from "../../firebase";
export default function useCurrentUserDetails() {
const uid = firebase.auth().currentUser?.uid;
const [data, setData] = useState(null);
useEffect(() => {
if (!uid) {
setData(null);
return;
}
const unsubscribe = firebase.firestore().collection("users").doc(uid).onSnapshot((snapshot) => {
if (snapshot.exists) {
setData(snapshot.data());
} else {
setData(null);
}
})
return unsubscribe;
}, [uid]);
const updateCurrentUserData = (newData) =>
firebase.firestore().collection("users").doc(uid).set(newData).then(() => {
Alert.alert(
'Profile Updated',
'Nice work!',
[
{ text: 'OK' }
],
{ cancelable: false }
);
}).catch((error) => {
Alert.alert(
'Something went wrong',
'Give it another go',
[
{ text: 'OK' }
],
{ cancelable: false }
);
});
return [data, updateCurrentUserData];
}
In my view im calling the following:
const [currentUserDetails, setCurrentUserDetails] = useCurrentUserDetails();
And on a button press, the values are updated:
const handleSubmit = async () => {
setLoading(true)
const url = await uploadImage(profileURL, firebase.auth().currentUser?.uid)
await setCurrentUserDetails(
{ ...currentUserDetails,
username,
age: Number(age),
height,
country,
occupation,
profileURL: url,
}
)
setLoading(false)
}
The issue im having is that for the first time the user try's to edit the data, it won't update unless I have all the fields present. How could I make each value optional?
For example lets say the person only updated the occupation it will update it and leave the rest and not create any fields for this in the db.
I don't know if how I did this is a correct approach.
y is destructured into separate fields.
const updateUserProfile = (y) => {
const { Age, Hobby, UserName, Job, Country, Name } = y;
return firestore.collection("users").doc(`${currentUser.uid}`).update({
UserName: UserName,
Hobby: Hobby,
Name: Name,
Age: Age,
Country: Country,
Job: Job,
});
};
This way I can update any field I want. But in my case all of these fields already have
default data written, like UserName : UserName, Hobby: hobby. And then I update it with what user changed.
This is how I get "y"
userData is what I get from firestore.
const [data, setData] = useState({ [name]: value });
const x = userData;
const y = Object.assign(x, data);
Sorry if that is a mess of a answer.
I have functions that Sign up users but i can't seemed why it is not storing the users information in firebase firestore.
My Sign Up user functions:
//Signing up the user
export function* signUpUser({ payload: { displayName, surname, email, password, cellphone, address, province, tcCheckbox} }) {
try {
const user = yield auth.createUserWithEmailAndPassword(email, password);
const additionalData = { displayName, surname, cellphone, address, province, tcCheckbox};
yield getSnapshotFromUserAuth(user, additionalData);
} catch(err) {
console.log(err);
}
};
From my Sign up user function I get 'getSnapshotFromUserAuth'
export function* getSnapshotFromUserAuth(user, additionalData = {}) {
try {
const userRef = yield call(handleUserProfile, { userAuth: user, additionalData });
const snapshot = yield userRef.get();
yield put(signInSuccess({
id: snapshot.id,
...snapshot.data()
}))
} catch(err) {
console.log(err);
}
};
Which calls handleUserProfile in my firebase utils.js
export const auth = firebase.auth();
export const firestore = firebase.firestore();
export const handleUserProfile = async ({ userAuth, additionalData }) => {
if (!userAuth) return;
const { uid } = userAuth;
const userRef = firestore.doc(`users/${uid}`);
const snapshot = await userRef.get();
if (!snapshot.exists) {
const { displayName, surname, email } = userAuth;
const timestamp = new Date();
const userRoles = ['user'];
try {
await userRef.set({
displayName,
email,
surname,
createdDate: timestamp,
userRoles,
...additionalData
});
} catch(err) {
console.log(err);
}
}
return userRef;
};
The code is signing up the user but not storing all the additional data that i want to store with the user
I would think this is related with createUserWithEmailAndPassword. This method returns object UserCredential. Please check the reference and example.
So in the code there is user assignment and I think this should be corrected to:
const user = yield auth.createUserWithEmailAndPassword(email, password).user;
More or less like in the example. I do not have a playground to test it, so please share the result.
const { displayName, email, surname, cellphone, address, province, tcCheckbox } = userAuth;
did you mean additionalData instead of userAuth?
When a new user signs up, I want to create a number of relations to be automatically be set up as well. Currently, I am doing it this way:
const user = await User.create({
email,
password: hashedPassword,
}).save();
const settings = await Settings.create({
userId: user.id,
}).save();
const settings = await Profile.create({
userId: user.id,
}).save();
From what I've read, transactions are the best way to handle dependant operations, but PG doesn't create user ID until it is saved.
This is the best I could come up with transactions:
const user = await User.create({
email,
password: hashedPassword,
}).save();
const settings = await Settings.create({
userId: user.id,
});
const profile = await Profile.create({
userId: user.id,
});
await getConnection().transaction(async (transactionalEntityManager) => {
await transactionalEntityManager.save(settings);
await transactionalEntityManager.save(profile);
});
However, there is still the possibility that the user saves, but the relations do not.
Is there a better (or easier way) to handle this?
user.entity.ts
#Entity()
export class User {
#PrimaryGeneratedColumn()
id!: number;
#CreateDateColumn()
createdAt: Date;
#UpdateDateColumn()
updatedAt: Date;
#Column({ unique: true })
email!: string;
#Column()
password!: string;
#OneToOne(() => Settings, (settings) => settings.user)
settings: Settings;
#OneToOne(() => Profile, (profile) => profile.user)
profile: Profile;
}
settings.entity.ts
#Entity()
export class Settings {
#PrimaryColumn()
userId: number;
#OneToOne(() => User, (user) => user.settings)
user: User;
}
profile.entity.ts
#Entity()
export class Profile {
#PrimaryColumn()
userId: number;
#OneToOne(() => User, (user) => user.profile)
user: User;
}
The save method loads the relations before INSERT, therefore it is not possible to perform save for settings, The generated query will first try to SELECT the user and only then INSERT settings, as you said - inside a transaction it is not possible to select uncommitted records, and that why it doesn't work.
What can be done? Two options:
Save the entities nested
const user = await getConnection()
.transaction((transactionalEntityManager) => {
const userObj = User.create({
email,
password: hashedPassword,
})
userObj.profile = Profile.create({});
userObj.settings = Settings.create({});
return transactionalEntityManager.save(User, userObj);
});
Insert the user, and then insert the settings and profile
const userInsertResult = await getConnection()
.transaction(async (transactionalEntityManager) => {
const userInsertResult = await transactionalEntityManager
.createQueryBuilder(User,'user')
.insert()
.into(User)
.values({
email,
password: hashedPassword
}).execute();
const settingsInsertResult = await transactionalEntityManager
.createQueryBuilder(Settings,'settings')
.insert()
.into(Settings)
.values({
userId: userInsertResult.raw.id
}).execute();
const profileInsertResult = await transactionalEntityManager
.createQueryBuilder(Profile,'profile')
.insert()
.into(Profile)
.values({
userId: userInsertResult.raw.id
}).execute();
return userInsertResult
});
I have two functions that trigger onCreate and onUpdate however, the {uid} in onUpdate is returning undefined, whilst onCreate returns the {uid}.
How can I get the {uid} to work for onUpdate?
onUpdate.f.js - {uid} is undefined
exports = module.exports = functions.firestore
.document('users/{uid}/alerts/{name}') //UID is the User ID value stored in alerts
.onUpdate(snap => {
const user = snap.data();
console.log(user);
const msg = {
to: user.email,
from: 'notifications#example.com',
templateId: user.template,
dynamic_template_data: {
firstName: user.firstName,
email: user.email,
id: user.uid
}
};
return sgMail.send(msg).catch(err => console.log(`${user.email} - ${err}`));
});
onCreate.f.js - {uid} is correct
exports = module.exports = functions.firestore
.document('users/{uid}/alerts/{name}')
.onCreate(snap => {
const user = snap.data();
console.log(user);
const msg = {
to: user.email,
from: 'notifications#example.com',
templateId: user.template,
dynamic_template_data: {
firstName: user.firstName,
email: user.email,
id: user.uid
}
};
return sgMail.send(msg).catch(err => console.log(`${user.email} - ${err}`));
});
Fields in doc Alerts from frontend
doCreateAlert = (id, email, firstName, lastName, alertType, transactionEmailId) => {
const db = this.firestore;
return db.doc(`users/${id}/alerts/${alertType}`).set({
uid: id,
name: alertType,
email: email,
firstName: firstName,
lastName: lastName,
template: transactionEmailId,
dateCreated: new Date(),
dateModified: new Date()
});
};
The onUpdate is triggered by updating the database with onClick={this.updateAlert} as
updateAlert = () => {
const { firebase, userID } = this.props;
const companyTypeSetup = db.doc(`users/${userID}/alerts/emailVerified`);
companyTypeSetup.update({
dateModified: new Date()
});
};
on the frontend I receive the error of
Uncaught (in promise) Error: No document to update: projects/app/databases/(default)/documents/users/undefined/alerts/emailVerified
and the function is never run. If I manually update the doc in Firestore, I get an error in the firebase functions log as
TypeError: snap.data is not a function
at module.exports.functions.firestore.document.onUpdate.snap (/user_code/lib/auth/onUpdate.f.js:17:23)
at cloudFunctionNewSignature (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:105:23)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:135:20)
at /var/tmp/worker/worker.js:754:24
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
As the guide shows, onUpdate has two parameters: change and context. You use change since you may want to access the value before the update or after the update. Assuming you want the value after the update, that would look like this:
exports = module.exports = functions.firestore
.document('users/{uid}/alerts/{name}') //UID is the User ID value stored in alerts
.onUpdate((change, context) => {
const user = change.after.data();
console.log(user);
const msg = {
to: user.email,
from: 'notifications#example.com',
templateId: user.template,
dynamic_template_data: {
firstName: user.firstName,
email: user.email,
id: user.uid
}
};
return sgMail.send(msg).catch(err => console.log(`${user.email} - ${err}`));
});
Problem can easily be solved by reading the documents at Handle Event Data. However, if you are like me and skim the documents then the solution is
.onUpdate(change => {
const user = change.after.data();