Update Profile During SignUp Angular Firebase - javascript

I am using angularfire/firebase for an Angular project where I am working through authentication.
When a user signs up, the following function is called:
signup(email: string, password: string, name: string) {
return this.fireAuth
.createUserWithEmailAndPassword(email, password)
.then((res) => {
res.user?.updateProfile({
displayName: name,
});
// set vars and local storage
});
}
In my app.component, I have the following subscription to track changes and store in my state management (I don't think StateMgmt is the issue here):
ngOnInit() {
this.fireAuth.authState.user.subscribe((res) => {
if(res) {
console.log(res);
console.log(res.displayName);
this.store.dispatch(new SetUser(res));
}
}
When a user signs up, the name they enter upon signup is set through the res.user.updateProfile(... function, and since the authState had a change, the subscription in the app.component prints out an object and stores to the SetUser(res) state.
The console.log() from here in the app state prints out a large object, including the email, uid, and displayName. This is then stored in my state object. However, on theconsole.log(res.displayName), the result is null until I refresh the page triggering an "auto logon" where the displayName is fetched from firebase.
It seems to me the object of the .then((res)... from my signUp() updateProfile is not being triggered until after the user is already changed and stored.
Is there a way to make this signUp function work so that the SetUser(res) is only dispatched once the updateProfile({... is complete and the displayName is no longer null?
From my app.component console.log(res):
{
"uid": "vbn6TA8vTiLi7J2",
"displayName": "my name",
"photoURL": null,
"email": "email#email.com",
...
}
app.component console.log(res.displayName):
null
EDIT
Another interesting thing I found is that when I take a copy of the res and print it out, the displayName is also null.
const r = JSON.parse(JSON.stringify(res));
console.log(res);
console.log(r);
the console.log(r) has an object where the displayName is null. The console.log(res) has an object where the displayName is NOT null and is the name passed in to the signUp(... function.

This maybe a race condition within Angular Fire, I would ensure that the user is finalized before updating the user.
async UpdateProfile(displayName: string) {
const profile = {
displayName: stored.username,
photoURL: stored.profile,
email: stored.email,
}
return (await this.afAuth.currentUser).updateProfile(profile);
}
I am making the assumption you are storing the user details via a form object as stored but this can easily be from the auth provider or a custom object

Even waiting for the user to be finalized via async await before moving on did not work.
What I ended up doing was the following:
In my signUp() function, once the user was created, I retrieved the current use via this.fireAuth.authState (fireAuth is from AngularFireAuth in my constructor). I subscribed to this, while piping to only take the first value, and then dispatched my SetUser function. This ensures that the function is only running after the updateProfile is complete using a .then().
Because I moved the state dispatch into my signup function, I removed the subscription from my AppComponent.
Here is my signup function from the auth service:
...
constructor(
public fireAuth: AngularFireAuth,
) {
this.user = this.fireAuth.authState;
}
signup(email: string, password: string, name: string) {
return this.fireAuth
.createUserWithEmailAndPassword(email, password)
.then((res) => {
res.user
?.updateProfile({
displayName: name,
})
.then(() => {
this.user.pipe(first()).subscribe((res) => {
this.store.dispatch(new SetUser(res));
});
});
});
}

Related

Firebase database user info

I’m trying to make a website where a user can login with a username a password and a name
This function is not implemented in firebase authentication option so I tried using its real-time database
Here is my code so far:
export function signup(email, password, name) {
return auth().createUserWithEmailAndPassword(email, password).then((userCredential) => {
db.ref("users").ref(userCredential.uid).push({
name: name
});
})
}
Saving the data does not work but I think I have a good start.
Try passing only one ref() call and set() instead of push():
export function signup(email, password, name) {
return auth().createUserWithEmailAndPassword(email, password).then((userCredential) => {
db.ref("users/" + userCredential.user.uid).set({
name: name
});
})
}

React - get request using axios not able to setState

I am new to react and currently working on a project.
I usually use Class Components, however, I am trying to learn Functional Components but I encounter an issue with the setState.
My Component:
function GetCustomerDetailsFc(this: any, props: CustomerModel): JSX.Element {
const [state, setState] = useState('');
async function send(){
try {
const response = await axios.get<CustomerModel>(globals.urls.customerDetails)
store.dispatch(oneCustomerAction(response.data));
console.log(response.data)
setState( {customer: response.data});
} catch (err) {
notify.error(err);
}
}
useEffect(() => {
send();
});
return (
<><h1>{this.state.customer.email}</h1></>
);
}
export default GetCustomerDetailsFc;
I am not sure on how to save state so I can read it.
when I log the request I can see the back-end actually returns data which contains:
User details : (id, email, password , etc...)
An Array of Coupons linked to users purchase.
so basically I can see the request responds properly, however, since I am new I am not sure exactly about the syntax and how I could read properly the data.
Customer Model:
class CustomerModel {
public id: number;
public firstName :string;
public lastName :string;
public email: string;
public token: string;
public password: string;
}
export default CustomerModel;
Note:: I use redux for this project and store data accordingly.
Thanks.
If the returned data is intended to be used locally in this component, then what you pass into useState should match the response you are receiving from the server.
So if for instance your server returns a CostomerModel object, you should initiate your useState with an object -
const [ customer, setCustomer ] = useState({})
try{
const response = await axios.get<CustomerModel>(globals.urls.customerDetails)
...
setCustomer(response);
...
}

Firebase Cannot read property 'uid' of null

For some reason, firebase can't read the property UID. Any help would be greatly appreciated! Should I be changing how I am getting the data from firebase.auth in the first place? The end goal is to update a firestore database file with the ID of the user UID.
// Add additional info to user account
const completeAccountform = document.querySelector('#wf-form-completeAccount');
document.getElementById("completeButton").addEventListener('click', addAccountinfo);
function addAccountinfo() {
// get new account data
const firstName = completeAccountform['firstName'].value;
const lastName = completeAccountform['lastName'].value;
const location = completeAccountform['location'].value;
const db = firebase.firestore();
firebase.auth().currentUser.uid;
db.collection('users').doc(user.uid).set({
email: signupForm['signupEmail'].value,
firstname: completeAccountform['firstName'].value,
lastname: completeAccountform['lastName'].value,
location: completeAccountform['location'].value
}).then(function(docRef) {
modalContainer.style.display = 'none'
console.log("Document updated with ID: ", docRef.id);
})
.catch(function(error) {
console.error("Error updating document: ", error);
});
form.reset()
};
I'd suggest you to follow the recoomendations at the Firebase docs for retrieveng the current user, as it i specified:
The recommended way to get the current user is by setting an observer
on the Auth object:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
By using an observer, you ensure that the Auth object isn't in an
intermediate state—such as initialization—when you get the current
user. When you use signInWithRedirect, the onAuthStateChanged observer
waits until getRedirectResult resolves before triggering.
I'd recommend you to use the observer, because sometimes the auth object has not finished initializing and it'll be null (then you'll have this error). It depends on your Auth flow and what fits your user case better

Factory tries to create two objects with same properties

I'm writing tests for an express app that implements a user CRUD. Before writing integration tests, I made a factory that would create users for the tests:
factories.js
import faker from 'faker';
import { factory } from 'factory-girl';
import User from '../../src/app/models/User';
factory.define('User', User, {
name: faker.name.firstName(),
email: faker.internet.email(),
password: faker.internet.password(),
admin: false,
});
export default factory;
Nice. Now whenever I needed to create a user for tests, I would just have to use factory.create(), right? Wrong. In one of the test suites I need two users, one that is admin and other that isn't. So I wrote this setup/teardown:
let user, admin;
const createUsers = async () => {
// Runs ok
user = await factory.create('User');
// Error
admin = await factory.create('User', { admin: true });
};
const removeUsers = async () => {
await user.remove();
await admin.remove();
};
beforeAll(async () => {
await createUsers();
});
afterAll(async () => {
await removeUsers();
mongoose.connection.close();
});
user = await factory.create(...) runs fine, but admin = await factory.create(...) raises a MongoError: E11000 duplicate key error collection.
This is because the factory tries to create a user with an email that is already in use (email is unique for the model User). I could ensure that wouldn't happen by passing a specific email to the create method in the same way I passed admin: true, but that wouldn't make much sense since I created the factory to avoid this kind of trouble.
Can you guys point out what am I doing wrong here? I guess it has something to do with the way I defined my factory. Thanks in advance.
EDIT: As suggested, I fixed it by using factory.sequence.
factory.define('User', User, {
name: factory.sequence('User.name', () => faker.name.firstName()),
lastName: factory.sequence('User.lastName', () => faker.name.lastName()),
email: factory.sequence('User.email', () => faker.internet.email()),
password: factory.sequence('User.password', () => faker.internet.password()),
redefinePassword: false,
admin: false,
});
While faker.internet.email() will create a new fake email every time it is called, you only call it once when defining your template object for the factory. Look into the factory.sequence API for a way to make the factory run some code for each object being created https://www.npmjs.com/package/factory-girl#defining-factories
Or simply pass the function faker.internet.email, without the () and I think factory-girl will call that function each time as well, you can also make your define call take a function that returns this object (after calling faker.internet.email()), so many options!

Middleware to Vuex state not updating state?

I am trying to get my firestore data that I am getting to be stored in my state but it does not show up in my Vue dev tools in the state.
When I console.log() the data I am getting through the store action I can see I am getting the right data but it will not update the state.
I am using middle-ware on my home page and another page to dispatch my action in order to get the required data.
I have also used a conditional statement within the middle-ware below to try to only dispatch action when my other state variables are not null because the firestore query requires state.user
//this is check-auth middleware
export default function(context) {
// in initAuth we are forwarding it the req
context.store.dispatch('initAuth', context.req)
console.log('WE ARE GRABBING USER INFO')
context.store.dispatch('grabUserInfo', context.req)
console.log('There is already user info there')
// context.store.dispatch('currentUser')
}
We are dispatching grabUserInfo to run a action that has a firestore query in it.
grabUserInfo(vuexContext, context) {
let userInfo = []
var userRef = db.collection('users')
var query = userRef
.where('user_id', '==', vuexContext.state.user)
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
console.log(doc.data())
userInfo.push(doc.data())
})
})
vuexContext.commit('setUserInfoSub', userInfo)
}
my
console.log(doc.data()) is showing
subscribers: ["noFace2"]
subscriptions: ["noFace3"]
user_id: "VbomJvANYDNe3Bek0suySs1L8oy1"
username: "noFace1"
my info should be going through a mutation and commiting to state, but it does not show up in my state vue dev tools.
setUserInfoSub(state, payload) {
state.userInfoSub = payload
}
I don't understand how the data is not ending up in my state. Here is my State and mutations.
const createStore = () => {
return new Vuex.Store({
state: {
loadedCards: [],
user: null,
username: null,
userInfoSub: [],
token: null
},
mutations: {
setCards(state, cards) {
state.loadedCards = cards
},
setUser(state, payload) {
state.user = payload
},
setUsername(state, payload) {
state.username = payload
},
setUserInfoSub(state, payload) {
state.userInfoSub = payload
},
setToken(state, token) {
state.token = token
}
Change your mutation to this:
setUserInfoSub(state, payload) {
Vue.set(state, 'userInfoSub', payload);
}
This will allow Vue's reactivity system to kick back in for the state variable reassignment.
Per #Alexander's comment, you should also move the commit() inside then() given the async nature of the Firebase query.

Categories

Resources