I want to set user from auth to database. I do it by .set and it is working good but if I have some fields for one user, then all data are reset when user will sign in again. For this example collection -> users -> docs: (name, color). The name will be static after login but color could be changing and remember to the next logging.
On the login I set:
const signIn = () => {
auth
.signInWithPopup(provider)
.then(result => {
dispatch({
type: actionTypes.SET_USER,
user: result.user
});
db.collection('users').doc(result.user.uid)
.set({
name: result.user.displayName,
color: 'blue'
})
// add user list
db.collection('users').onSnapshot(snapshot => (
dispatch({
type: actionTypes.SET_USER_LIST,
payload: (
snapshot.docs.map(doc => ({
data: doc.data()
}))
)
})
))
})
.catch(error => alert(error.message));
}
And in another file when the user clicks on the setting and changes his color is also changing in the database, but when he login again the function signIn reset this color.
const changeColor = e => {
let colorVal = e.target.value;
db.collection('users').doc(user.uid).update({
color: colorVal
})
setOpenColors(!openColors)
};
So question is how to check if(userExist)/ filter or sth like this between .doc(result.user.uid)
and set(). I tried to do this by .where in firebase, setting data to reducer but always was the same result, so I decide to ask you.
If I correctly understand your question, you should query for the user doc in the signIn() function, in order to check if it already exists or not, as follows:
const signIn = () => {
auth
.signInWithPopup(provider)
.then(result => {
dispatch({
type: actionTypes.SET_USER,
user: result.user
});
db.collection('users').doc(result.user.uid)get()
.then((doc) => {
if (!doc.exists) {
return db.collection('users').doc(result.user.uid)
.set({
name: result.user.displayName,
color: 'blue'
});
} else {
return null;
}
})
.then(() => {
db.collection('users').onSnapshot(snapshot => (
dispatch({
type: actionTypes.SET_USER_LIST,
payload: ( snapshot.docs.map(doc => ({data: doc.data()})))
})
})
})
.catch(error => alert(error.message));
}
Related
I have an authentication action on an react native app which must during the authentication go to perform another action but it is never executed (dispatch(getMobiles())). I do not understand why. Do you have an idea ?
If my authentication went well, I immediately want to retrieve data on my new users, so I want to execute getMobiles () which is another action.
thanks in advance :)
auth actions
export const authentication = (
username: String,
password: String,
label: String,
synchro: Boolean,
url: String,
) => {
return dispatch => {
dispatch({type: LOGIN.PENDING, payload: ''});
const type = UDA_URL_LIST.map(uda => {
if (uda.url === url) {
return uda.name;
}
})
.join()
.replace(/[, ]+/g, ' ')
.trim();
fetchUser(url, username.trim(), password.trim())
.then(response => {
if (!response.err) {
const newUser = {
...response,
label,
type,
synchro,
};
dispatch({type: LOGIN.SUCCESS, payload: newUser});
// not dispatched !
return dispatch(getMobiles(url, response.key, newUser.userId));
}
})
.catch(err => dispatch({type: LOGIN.ERROR, payload: err}));
};
};
getMobiles
export const getMobiles = (
url: String | null = null,
token: String,
userId: String,
) => {
return dispatch => {
dispatch({type: MOBILES.PENDING, payload: ''});
fetchMobiles(url, token)
.then(mobilesList => {
dispatch({
type: MOBILES.SUCCESS,
payload: mobilesList.data,
meta: {userId},
});
})
.catch(err => alert(err));
};
};
};
Your code in getMobiles require second call with parametr dispatch,
try to use getMobiles(url, response.key, newUser.userId)(dispatch)
I'm using useReducer to update the errorsState when user logged in and failed. I've read many solutions and it was said that dispatch is async and I know that so I put console.log inside the useEffect to see the errorsState change, but unfortunately it didn't changed. Here's my code
Login.jsx
export default function Login({ userProps }) {
//
// some variables and state
//
const { loading, user } = useLogin({ email: state.email }, state.submitted)
const [errors, dispatch] = useReducer(errorsReducer, errorsState)
useEffect(() => {
console.log("errors", errors) // it won't triggered because errors state didn't updating from UseLogin
}, [errors])
return content
}
Here is fetch function useLogin
AuthAction.js
export const useLogin = (data, submitted) => {
const [state, dispatch] = useReducer(userReducer, userState)
const [errors, errorsDispatch] = useReducer(errorsReducer, errorsState)
useEffect(() => {
if (!submitted) return
dispatch({
type: USER_ACTIONS.MAKE_REQUEST,
})
ticketApi.login(data).then(({ res, status }) => {
if (status !== "failed") {
// Save to local storage
const { token } = res
// set token to local storage
localStorage.setItem("jwtToken", token)
// Set token to Auth Header
setAuthToken(token)
// decode token to get user data with jwt-decode
const decoded = jwt_decode(token)
// set current user
return dispatch({
type: USER_ACTIONS.GET_USER,
payload: decoded,
})
}
dispatch({
type: USER_ACTIONS.END_REQUEST,
})
return errorsDispatch({
type: ERRORS_ACTIONS.GET_ERRORS,
payload: res.response.data,
})
})
}, [submitted])
return state
}
I've tried put console.log inside the ERRORS_ACTIONS.GET_ERRORS to see the response, and it was fine.
So where did i go wrong?
useReducer allows you to better manage complex states, it's not a state container, what you're doing there is to create 2 different states, one inside useLogin and the other in your Login component, return errors from your useLogin hook so the Login component can see it.
Login
export default function Login({ userProps }) {
//
// some variables and state
//
const { loading, user, errors } = useLogin({ email: state.email }, state.submitted)
useEffect(() => {
console.log("errors", errors)
}, [errors])
return content
}
useLogin
export const useLogin = (data, submitted) => {
const [state, dispatch] = useReducer(userReducer, userState)
const [errors, errorsDispatch] = useReducer(errorsReducer, errorsState)
useEffect(() => {
if (!submitted) return
dispatch({
type: USER_ACTIONS.MAKE_REQUEST,
})
ticketApi.login(data).then(({ res, status }) => {
if (status !== "failed") {
// Save to local storage
const { token } = res
// set token to local storage
localStorage.setItem("jwtToken", token)
// Set token to Auth Header
setAuthToken(token)
// decode token to get user data with jwt-decode
const decoded = jwt_decode(token)
// set current user
return dispatch({
type: USER_ACTIONS.GET_USER,
payload: decoded,
})
}
dispatch({
type: USER_ACTIONS.END_REQUEST,
})
return errorsDispatch({
type: ERRORS_ACTIONS.GET_ERRORS,
payload: res.response.data,
})
})
}, [submitted])
return { ...state, errors };
}
I have an action that currently works fine using .then but when I try and convert it to async ... await it suddenly can't access the first parameter of the function. The second parameter still works fine though.
Current function that works fine:
export const signInUser = (email, password) => {
return (dispatch) => {
return firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(() => {
console.log('signed in')
const { uid, email } = firebase.auth().currentUser
dispatch({ type: 'SIGN_IN', uid, email })
return dispatch(fetchAllData())
})
.catch(error => {
throw (error)
})
}
};
New function that doesn't work because 'email' is undefined.
export const signInUser = (email, password) => {
return async (dispatch) => {
console.log('testing')
console.log(password)
console.log('testing', email, password)
await firebase.auth().signInWithEmailAndPassword(email, password)
console.log('signed in')
const { uid, email } = firebase.auth().currentUser
dispatch({ type: 'SIGN_IN', uid, email })
return dispatch(fetchAllData())
}
};
The first console log of console.log('testing') works fine and outputs the string 'testing'. The second console log of console.log(password) also works fine and prints the inputted password. But the third console log console.log('testing', email, password) doesn't get shown at all.
This is how the function is called:
Parent component passes it to child component:
submit={(email, password) => dispatch(signInUser(email, password))}
Child component calls it:
const submitForm = (event) => {
event.preventDefault();
if (validForm) {
setLoading(true)
submit(email, password)
.catch(err => {
setLoading(false)
console.log('catch:', err.code)
setError(err.code)
})
}
}
The output I am receiving is catch: undefined.
Also, if I change the function to this:
export const signInUser = (email, password) => {
const userEmail = email
return async (dispatch) => {
console.log('testing')
console.log(password)
console.log('testing', userEmail, password)
await firebase.auth().signInWithEmailAndPassword(userEmail, password)
console.log('signed in')
const { uid, email } = firebase.auth().currentUser
dispatch({ type: 'SIGN_IN', uid, email })
return dispatch(fetchAllData())
}
};
Then it works fine. But I don't know why I would need to change it like this?
Thanks
Ok so the reason it wasn't working was because on the next line I was declaring email like this const { uid, email } = firebase.auth().currentUser
which was overriding the function parameter of email and therefore giving me the error that i was using email before it was declared.
I have built a feature that allows a logged in User to update their email. When I change the email and redirect to a different page the email that is displayed is the old email, and only when I refresh the page does the new email appear. I believe the the best way to address this is for the system to re-authenticate the User (I may be wrong and am open to suggestions).
// UpdateEmail.js
handleSubmit(e) {
e.preventDefault()
this.props.updateEmail(this.state.newEmail)
this.props.history.push('/settings')
}
const mapDispatchToProps = (dispatch) => {
return {
updateEmail: (newEmail) => dispatch(updateEmail(newEmail))
}
}
// authActions.js *NEW*
export const updateEmail = (newEmail, oldEmail, password) => {
return (dispatch, getState, {getFirebase, getFirestore}) => {
const firebase = getFirebase();
let user = firebase.auth().currentUser
let credential = firebase.auth.EmailAuthProvider.credential(oldEmail, password);
user.reauthenticateAndRetrieveDataWithCredential(credential)
.then(() => {
user.updateEmail(
newEmail
).then(() => {
dispatch({ type: 'UPDATE_LOGIN_DETAILS_SUCCESS'})
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
dispatch({
type: 'CHECK_REAUTH_SUCCESS',
user: user
})
} else {
dispatch({ type: 'CHECK_REAUTH_ERROR'})
}
});
})
.catch(err => {
dispatch({ type: 'UPDATE_LOGIN_DETAILS_ERROR'}, err)
})
})
.catch(err => {
dispatch({ type: 'REAUTH_ERROR'}, err)
})
}
}
// Settings.js this is where the user is redirected after CHECK_REAUTH_SUCCESS
const mapStateToProps = (state) => {
return {
licenses: state.firestore.ordered.licenses,
aircraft: state.firestore.ordered.aircraft,
profile: state.firebase.profile,
auth: state.firebase.auth
}
}
// authActions.js *OLD*
export const updateEmail = (newEmail) => {
return (dispatch, getState, {getFirebase, getFirestore}) => {
const firebase = getFirebase();
const user = firebase.auth().currentUser
user.updateEmail(
newEmail
).then(() => {
dispatch({ type: 'UPDATE_LOGIN_EMAIL_SUCCESS'})
}).catch(err => {
dispatch({ type: 'UPDATE_LOGIN_EMAIL_ERROR', err })
})
}
}
I am using Turbo which you can find more information about it here: https://www.turbo360.co/docs
What I am trying to do is to attach a parameter to a Post before it is created. In this case I am trying to attach a profile. I am not getting any errors and from what I see the param is going through just fine, but when I log out the post the profile param is not there.
Here is creating the post:
createPost(params) {
const { currentUser } = this.props.user;
if (currentUser == null) {
swal({
title: 'Oops...',
text: 'Please Login or Register before posting',
type: 'error'
});
return;
}
params['profile'] = currentUser;
console.log(params);
this.props
.createPost(params)
.then(data => {
swal({
title: 'Post Created',
text: `Title: ${data.title}`,
type: 'success'
});
})
.catch(err => console.log(err));
}
Here is the action createPost:
createPost: params => {
return dispatch => {
return dispatch(TurboClient.createPost(params, constants.POST_CREATED));
};
},
Here is the TurboClient function createPost:
const postRequest = (resource, params, actionType) => {
return dispatch =>
turbo({ site_id: APP_ID })
.create(resource, params)
.then(data => {
if (actionType != null) {
dispatch({
type: actionType,
data: data
});
}
return data;
})
.catch(err => {
throw err;
});
};
const createPost = (params, actionType) => {
return postRequest('post', params, actionType);
};
Now from here you can see where I log the params, this returns:
Here is what the post looks like once it is created:
It looks like you're trying to create a Post object. In your createPost method you return:
postRequest('post', params, actionType);
By using the word 'post' here you are creating it as a Post object, which has a very specific schema that it follows. If you would like to change that, you could try creating a Custom Object by doing something like this, for example:
postRequest('randomName', params, actionType);
Hope that helps.