I am using Firebase.auth() to authenticate a login with Google Firebase then I retrieve the UID and send it to my Redux Store. The UID is not being sent to the store unless I navigate to the next page then return to the login page. It seems my order of operations is off, how can I get the UID in my Redux store without haveing to re-login/ refresh the page.
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
id: ''
}
}
id (value) {
this.props.id(value);
}
handleLogin = (load) => {
const { email, password } = this.state
Firebase.auth()
.signInWithEmailAndPassword(email, password)
.then(async cred => {
return db.collection('users').doc(cred.user.uid).set({
test: 'test'
})
})
.then(() => this.props.navigation.navigate('AddProfiles'))
.catch(error => console.log(error))
const currentUser = firebase.auth().currentUser;
const userId = currentUser["uid"];
this.setState({
id: userId
})
this.props.id(this.state.id);
}
<TouchableOpacity style={styles.signupbutton}>
<Button
color='white'
title="Log in"
onPress={(payload) => this.handleLogin()}
/>
</TouchableOpacity>
const mapStateToProps = (state) => {
return {
counter: state.counter,
value: state.id
};
}
const mapDispatchToProps = dispatch => {
return {
id: (value) => dispatch({ type: 'id', payload: value })
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Home)
Right now, the code starting with the line const currentUser is running before the signInWithEmailAndPassword completes, since signInWithEmailAndPassword is an asynchronous function. The reason that it works on refresh is at that point, firebase.auth().currentUser has a value, so
You can move your code inside the then block so that it runs only when the function is complete. It'll look something like this (untested, because I don't have the rest of your code):
handleLogin = (load) => {
const { email, password } = this.state
Firebase.auth()
.signInWithEmailAndPassword(email, password)
.then(async cred => {
this.setState({
id: cred.user.uid
})
this.props.id(cred.user.id);
return db.collection('users').doc(cred.user.uid).set({
test: 'test'
})
})
.then(() => this.props.navigation.navigate('AddProfiles'))
.catch(error => console.log(error))
}
Note that setState is also asynchronous, so calling this.props.id(this.state.id); right after setState is likely to fail on the first run.
Although the above should fix the immediate issue, I'd suggest onAuthStateChanged: https://firebase.google.com/docs/auth/web/start#set_an_authentication_state_observer_and_get_user_data
This way, you can do your sign in and set the Redux state based on its value or run the same code to set the Redux value when the user just returns to a page. It'll probably lead to a more robust situation than tying everything to signInWithEmailAndPassword
Related
I am using Firebase Realtime Database for a site I am developing with React. In a useEffect method, I am using Firebase's get method to receive all the data from the database and it works when I switch from the home page back to the page I am displaying the data on but it doesn't work when I refresh my page. I have tried using an async await function, console.logging everything I could think of, and re-writing the entire code.
This is my useEffect method that fetches an input that was previously saved to the database. If I switch from the 'Journal' Router page to Home page and back, it loads correctly but it doesn't load correctly if I refresh the page. When I refresh, it console.logs 'No Data' but I know the data exists because when I switch between router pages it does load.
useEffect(() => {
const dbRef = ref(getDatabase())
//Fetches dreams from firebase's database
get(child(dbRef, `/${user.uid}/dreams`)).then(snapshot => {
if (snapshot.exists()){
const dreams = snapshot.val()
Object.values(dreams).forEach(dream => {
setUserDreams(prev => [...prev, dream])
})
} else {
console.log('No Data')
}
}).catch(err => {
console.error(err);
})
...
}, [])
The JSON structure of the database is basically this
"USER_ID" : {
"dreams" : [{"RANDOM_UUID" : {...}}],
"tags" : [{"RANDOM_UUID" : {...}}]
}
The user ID is the uid that firebase generates in their user authentication feature and it doesn't change and the random uuid is a random string generated from the firebase uuidv4 method.
This is how the user variable is populated:
import {createContext, useContext, useEffect, useState} from 'react'
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
updateProfile,
onAuthStateChanged
} from 'firebase/auth';
import { auth } from '../firebase-config';
const UserContext = createContext();
export const AuthContextProvider = ({children}) => {
const [user, setUser] = useState({})
const createUser = (email, password) => {
return createUserWithEmailAndPassword(auth, email, password);
}
const updateUsername = (username) => {
return updateProfile(auth.currentUser, {
displayName: username
})
}
const signIn = (email, password) => {
return signInWithEmailAndPassword(auth, email, password);
}
const logout = () => {
return signOut(auth);
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
console.log(currentUser)
setUser(currentUser)
})
return () => {
unsubscribe()
}
}, [])
return (
<UserContext.Provider value={{createUser, user, logout, signIn, updateUsername}}>
{children}
</UserContext.Provider>
)
}
export const UserAuth = () => {
return useContext(UserContext)
}
Sorry if this is a bit weird but I figured out the issue. After logging the user variable in my journal file, I learned that it isn't populated until after that useEffect is ran so I just put user as the dependency variable in my useEffect hook so it waits until it is populated to run that hook.
useEffect(() => {
const dbRef = ref(getDatabase())
//Fetches dreams from firebase's database
get(child(dbRef, `/${user.uid}/dreams`)).then(snapshot => {
if (snapshot.exists()){
const dreams = snapshot.val()
Object.values(dreams).forEach(dream => {
setUserDreams(prev => [...prev, dream])
})
} else {
console.log('No Data')
}
}).catch(err => {
console.error(err);
})
...
}, [user])
This is what worked, the only thing changed was the dependency array. Meaning, the user variable was populated after the useEffect hook ran which is what made me have issues. Thanks for the commenter that helped me out!
I'm using React to create a login page which after logging in should keep track if the user is logged in. I figured using the react context and state hook would be easier than using something as big and complex as Redux.
I created a login function which works, and after a successfull login it should update my state in my context. The login works (i get a status 200) but my state is not updated in my context.
My 'AuthContext.jsx' looks like this
import React, { createContext, useState } from "react";
import { login, register } from "../API/apiMethods";
export const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [authenticated, setAuthenticated] = useState(false);
const authSetter = (state) => {
setAuthenticated(state);
};
const authGetter = () => {
return authenticated;
};
return (
<AuthContext.Provider
value={{
authSetter,
authGetter,
login,
register,
}}
>
{children}
</AuthContext.Provider>
);
};
My login function looks like this
/**
* #description Handles login
* #param {String} email
* #param {String} password
* #returns {Boolean}
*/
export const login = async(email, password) => {
try {
let authenticated = false;
await fetch(BASE_URL + "login", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
email,
password
}),
}).then((res) => {
authenticated = res.status === 200 ? true : false;
});
return authenticated;
} catch (err) {
console.log(err);
return false;
}
};
And in my login form i try to update the authentication boolean after a successfull login
const {
login,
authSetter,
authGetter
} = useContext(AuthContext);
const submit = async(e) => {
e.preventDefault();
await login(email, password)
.then((authSuccess) => {
if (authSuccess) {
console.log("login successfull");
authSetter(true);
}
})
.then(() => console.log(authGetter()));
};
With this code i expected the console output to be a printed string with 'login successfull' and a printed boolean true.
But it seems my state was not updated even though i did call the setter.
I don't know why it won't update, can anyone help me?
This piece of code does exactly what it is supposed to. When you update the state it does not happen immediately.
const submit = async(e) => {
e.preventDefault();
await login(email, password)
.then((authSuccess) => {
if (authSuccess) {
console.log("login successfull");
authSetter(true);
}
})
.then(() => console.log(authGetter()));
};
When you call the authSetter(true);, the state update is queued and once the then callback completes it goes to the next then in the chain which has your authGetter(). Now the state update does not happen immediately as I explained, it is queued. So when the last then callback is executed the state update which is queued has not happened and you still see false which is the old value.
You can refactor your AuthProvider in the following way, there is no need to wrap the setter in a function as it would create a new instance of the function when the state is updated (useState on the other hand returns a memoized value of the setter function) and you can simply return the authenticated state without the getter which again has the same issue.
import React, { createContext, useState } from "react";
import { login, register } from "../API/apiMethods";
export const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [authenticated, setAuthenticated] = useState(false);
return (
<AuthContext.Provider
value={{
setAuthenticated,
authenticated,
login,
register,
}}
>
{children}
</AuthContext.Provider>
);
};
In your form, you can have an extra useEffect to check whether you have logged in successfully. the useEffect will run when the authenticated state has been updated.
const {
login,
setAuthenticated,
authenticated
} = useContext(AuthContext);
const submit = async(e) => {
e.preventDefault();
const authSuccess = await login(email, password);
if (authSuccess) {
console.log("login successfull");
authSetter(true);
}
};
useEffect(() => {
console.log(authenticated);
}, [authenticated]);
I am trying to log something to my console on page load in react. I've never really used react and I've only done node.js before, so this is new to me.
I have this so far, but it doesn't seem to be working. It seems more js then react.
window.onload(console.log("logging this here"))
how would I do this?
more code on page:
class NormalLoginForm extends React.Component {
state = {
error: null,
}
handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (err) return
const email = this.props.form.getFieldValue('email')
const password = this.props.form.getFieldValue('password')
var accountStatus1 = ""
AuthorizationHome.doSignInWithEmailAndPassword(email, password)
.then(() => {
firebase.auth().onAuthStateChanged((user) => {
// window.onload(function (console.log("hello"))
For class component you can use;
ComponentDidMount(){
console.log("logging this here")
}
for functional components you can use;
useEffect(() => {
console.log("logging this here")
}, [])
it is impossible this way.
React component will render after window load event. So code will never trigger callback.
U need to change logic and just execute a callback if u want to call something in the component.
some example:
const ComponentWithAuth = () => {
const [user, setUser] = useState(null);
useEffect(() => {
getAuthUser().then(setUser)
});
if (user) {
return <PrivateComponent/>
}
return <LoginForm setUser={setUser}/>
}
const LoginForm = ({setUser) => {
const handleSubmit =async () => {
...
const user =await getAuthUser();
setUser(user);
}
...
}
I'm creating a new app where I want to be able to post updates to my friends. A micro-blogging site.
I want to learn how to update the app using React hooks and React's context API. I created the following provider that takes the state as the value... I want to be able to add a new post and then update the state's posts so that I don't have to fetch the database again (using firestore) I'm really trying to save myself a call to the db...
Basically, when I call createNewPost within the state, I want to be able to update the current posts section of the state: state.posts but when I update the state after the API call is successful, my entire posts array gets replaced for some reason. Not sure what I might be doing wrong...
import { createContext, useState } from 'react';
import { createDoc, getWhere } from '../utils/database/db';
export const PostDataContext = createContext();
const SetDataContextProvider = ({ children }) => {
const [state, setState] = useState({
posts: [],
timelinePosts: [],
createNewPost: async (collection, payload) => {
const doc = await createDoc(collection, payload)
payload.id = doc?.doc?.id;
updateStatePosts(payload);
return doc;
},
getPostsByUserId: async (userId) => {
const dataReceived = await getWhere('/posts', userId, 'userId')
setState({ ...state, posts: dataReceived })
}
});
const updateStatePosts = (payload) => {
console.log('why is state posts empty?!', state);
setState({ ...state, posts: [payload, ...state.posts] })
}
return <PostDataContext.Provider value={state}>
{children}
</PostDataContext.Provider>
}
export default SetDataContextProvider;
If I had to guess I would say you have a stale enclosure of your initial empty posts state within the updateStatePosts function used in your state. You can use a functional state update to access the previous state to update from. Functional state updates allow you to update from the previous state, not the state the update was enqueued/enclosed in.
const SetDataContextProvider = ({ children }) => {
const [state, setState] = useState({
posts: [],
timelinePosts: [],
createNewPost: async (collection, payload) => {
const doc = await createDoc(collection, payload)
payload.id = doc?.doc?.id;
updateStatePosts(payload);
return doc;
},
getPostsByUserId: async (userId) => {
const dataReceived = await getWhere('/posts', userId, 'userId')
setState(prevState => ({
...prevState, // <-- preserve any previous state
posts: dataReceived
}))
}
});
const updateStatePosts = (payload) => {
setState(prevState => ({ // <-- previous state to this update
...prevState,
posts: [payload, ...prevState.posts],
}));
};
return <PostDataContext.Provider value={state}>
{children}
</PostDataContext.Provider>
}
This function is wrong:
const updateStatePosts = (payload) => {
console.log('why is state posts empty?!', state);
setState({ ...state, posts: [payload, ...state.posts] })
}
You're using the spread operator correctly for your state, but posts is directly replaced. You might want to use the following setState:
setState({ ...state, posts: [...payload,...state.posts] })
Despite that, you should also refactor your state. Functions are not states so put them outside your state.
I am trying to print out user related items only.
So i am try to get items by requesting data to user id /api/items/:userid
I am using redux store
my server side code is like this
router.get("/:userid",(req, res) => {
// Item.find({ "owner.ownerName": `${req.params.userid}`})
Item.find({ "owner.id": `${req.params.userid}`})
.sort({
date: -1,
})
.then((items) => res.json(items));
console.log(req.user)
});
The problem is my front end request.
I don't know how to get user id inside ITEMACTION.
import {
GET_ITEMS,
ADD_ITEM,
DELETE_ITEM,
ITEMS_LOADING,
UPDATE_ITEM,
SUBSTRACT_ITEM,
} from "../actions/types";
import { tokenConfig } from "../actions/authActions";
import { returnErrors } from "../actions/errorActions";
import Axios from "axios";
export const getItems = () => (dispatch) => {
// will hit reducer
dispatch(setItemsLoading());
Axios.get("/api/items/")
.then((res) =>
dispatch({
type: GET_ITEMS,
payload: res.data,
})
)
.catch((err) => {
dispatch(returnErrors(err.response.data, err.response.status));
});
};
I actually tried to get user id from the redux store.
import store from '../store';
and inside getItems
store.getState().auth.user._id
the problem is that when i console.log in getItems the user id is always return null except first time after login. But when i look in redux dev tool. The user id is available
how can i get the userid
Hey you can get the getState as a second argument in the inner function along with the dispatch, using that you can access the updated state in an action.
Fixed Code:
export const getItems = () => (dispatch, getState) => {
// will hit reducer
const userId = getState().auth.user._id;
console.log(userId) // should output the updated data
dispatch(setItemsLoading());
Axios.get("/api/items/")
.then((res) =>
dispatch({
type: GET_ITEMS,
payload: res.data,
})
)
.catch((err) => {
dispatch(returnErrors(err.response.data, err.response.status));
});
};
store.getState doesn't return updated state, in order to get the updated state using store.getState() you need to subscribe to the state change.
const unsubscribe = store.subscribe(() => {
// logs the state data everytime an action is dispatched.
console.log("from listener: ", store.getState());
})
Details here