I am having a problem understanding how to implement:
sign a user in
keep listening to the user changes
sign a user out
unsubscribe to the listener
using :
const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
if (user) {
// Signed in
} else {
// Signed out
}
});
unsubscribe();
I understand that the above listener is recommended but I don't understand how to use it. Can someone explain in more details?
I wanna know if this function should be called when I first sign a user in, but then when and how should I sign them out? Should I call this function in each screen of the app (for ex. in componentDidMount)?
Moreover, where should I unsubscribe() to the listener? In which screen? Let's say there are THREE screens: Login, Screen 1, Screen 2. I call the listener in Login screen, then when the user is in Screen 1 or Screen 2, how am I supposed to keep listening to the changes and sign him out when I should AND unsubscribe to the listener?
firebase.auth().onAuthStateChanged opens up a listener, so a good idea is to make a useEffect that has the user as input, and change the user state according to if you are signed in or not. Once you use the sign out method, the onAuthStateChange will trigger. Then you can pass down the user everywhere through props o any global state you like.
useEffect(() => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
setUser(user)
} else {
setUser(null)
}
});
}, [user])
I use react-query instead of useEffect, and then I get the user claims, just to give you another example.
const fetchClaims = async () => {
setLoading(true);
const fetchedUser = await auth.onAuthStateChanged((user) => {
if (user) {
user.getIdTokenResult().then((idTokenResult) => {
// console.log(idTokenResult.claims);
setIsClient(idTokenResult.claims.client);
setIsMaster(idTokenResult.claims.master);
setNickName(idTokenResult.claims.name);
setUserEmail(idTokenResult.claims.email);
setClientName(idTokenResult.claims.clientName);
setIsPremium(idTokenResult.claims.premium);
setUserPicture(idTokenResult.claims.picture);
});
setUser(user);
setLoading(false);
} else {
setUser(null);
setIsClient(false);
setIsMaster(false);
setClientName(null);
setUserEmail(null);
setLoading(false);
setIsPremium(false);
}
});
return fetchedUser;
};
useQuery("fetchClaimsData", fetchClaims );
Related
I have the user inside localstorage, when user logouts the localstorage data becomes NULL. When user logins, the localstorages fills with user's data but to check this my userEffect in App.js do not reflect any change.
i have signUp
dispatch(signin(form, history));
history.push("/"); //go back to App.js file
in Navbar the user data changes
const Navbar = (props) => {
const [user, setUser] = useState(JSON.parse(localStorage.getItem("profile")));
const logout = () => {
dispatch({ type: "LOGOUT" });
dispatch({
type: "EMPTY_CART",
});
history.push("/");
setUser(null);
};
now at App.js i have
const user = JSON.parse(localStorage?.getItem("profile"));
const getCartItems = useCallback(async () => {
if (user) {
console.log("Yes user exixts");
dispatch(getEachUserCart(user?.result?._id));
} else {
console.log("No user exist");
}
}, []); //
useEffect(() => {
getCartItems();
}, [getCartItems]);
Now if u look above, after dispatching signUp action, i come back to App.js but here the useEffect don't run nor it checks if user have changed.
Hey – looks like you have a missing dependency issue. You need to have it like so
const getCartItems = useCallback(async () => {
if (user) {
console.log("Yes user exixts");
dispatch(getEachUserCart(user?.result?._id));
} else {
console.log("No user exist");
}
}, [user]);
Otherwise, this function will never be redeclared with the latest user value. Kindly let me know if that helps
The Context:
I want to know how to get rid of this error:
Warning: Can't perform a React state update on an unmounted component.
I think I know exactly why this error shows up:
I have a Signin Route. I have a litte piece of code in the useEffect that does this:
if (!auth.isEmpty && auth.isLoaded) {
history.push("/");
}
So when someone goes to mypage/signin and is already signed in, he gets redirected to Homepage. This works fine BUT:
The Problem:
When he is not signed in I have a little Signin Function:
const signin = async (e: React.MouseEvent) => {
setIsLoading(true);
e.preventDefault();
try {
const user = await firebase.login({ email, password });
setIsLoading(false);
if (user) {
history.push("/");
}
} catch (error) {
setIsLoading(false);
setError(error.message);
}
};
So when the users hits enter, he gets redirected to home when there is no error. It works fine but I get this error in the console, because I set the state and the the snippet in useEffect routes me to /Home, but the promise is not yet completed from firebase. And when it's finished it tries to set state, but component already unmounted.
What have I tried
I added a isMounted hook and changed my signin function to look like this:
const signin = async (e: React.MouseEvent) => {
e.preventDefault();
if (isMounted) {
setIsLoading(true);
try {
const user = await firebase.login({ email, password });
setIsLoading(false);
if (user) {
history.push("/");
}
} catch (error) {
setIsLoading(false);
setError(error.message);
}
}
};
But still the same error on route change.
Additional Info
Don't get confused about these 2 loading states auth.isLoaded (from react-redux-firebase) and isLoading (my own state). Basically why I did it this way is, because when someone is already logged in and then goes to /signin he sees the login form for a tiny moment, because firebase doesn't know yet if user is authenticated, so I handled it like this, so the user definetily sees a spinner and then gets redirected if already logged in.
How to solve this little problem?
You can use React hooks for this. The useEffect return method is called when compoment is unmonuted from screen. This is like compomentdidunmount in class based react.
declare global variable _isMounted to false. When useEffect is called, it changes to true and components are on screen.
If component are unmounted, then return method from useEffect is called and _isMounted is set to false;
while updating the state, you can check using _isMounted variable that is component is mounted or not.
var _isMounted = false;
const fetchuser = () => {
if(_isMounted)
{
// code
}
}
useEffect(() => {
_isMounted = true;
// your code;
return()
{
_isMounted = false;
console.log("Component Unmounted");
}
},[])
if you redirected after login, you dont have to change loading state. Just remove setIsLoading(false)
const user = await firebase.login({ email, password });
if (user) {
history.push("/");
}
I'm using React Native, Firebase and react-navigator.
In the LoadingScreen Component I observe the state changes (onAuthStateChanged)
componentDidMount() {
this.timer = setInterval(
() => firebase.auth().onAuthStateChanged(user => {
user ? this.props.navigation.navigate("App") : this.props.navigation.navigate("Auth");
}),
30,
);
}
In my AuthStack in the ChooseRole Component I have a function in which I want to register a user along with the role to be performed.
if (this.state.role === 'child') {
Firebase
.auth ()
.createUserWithEmailAndPassword (email, password)
.then (() => {
const addChildRole = fc.httpsCallable (('addChildRole'));
addChildRole ({email: this.state.email}). then ((result) =>
this.setState ({role: result}));
})
.catch (errorMessage => {
console.log (errorMessage)
});
})
The problem is that before .then() calls in witch I add a role, the Auth state probably changes and navigates to the application. In the AppStack, the Direction Component, based on the role, I want to target the child or parent component, but by calling
firebase.auth (). CurrentUser.getIdTokenResult ()
.then ((idTokenResult) => {
if (idTokenResult.claims.parent || idTokenResult.claims.child) {
}
idTokenResult.claims.parent and idTokenResult.claims.child gives undefined.
I want to handle giving users the role of registering and logging in, then moving to the appropriate component using Direction.js.
Do you have any idea how to solve this problem?
Not necessarily the cause of your problem, but too long to put in a comment.
There is no need to repeatedly call onAuthStateChanged. You can call it just once, and it will listen to auth state changes from that moment on. Calling it in short intervals as you're doing it bound to give problems.
Do this in componentDidMount instead:
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
user ? this.props.navigation.navigate("App") : this.props.navigation.navigate("Auth");
});
}
What could well be the cause of the problem is indeed that the onAuthStateChanged is fired before your database write completes. You can easily verify this by adding some logging statements.
If it is, you have two main options to fix the problem:
Don't use an onAuthStateChanged listener in componentDidMount, but simply check the value of firebase.auth().currentUser. This does not use a listener, so does not interfere with the database write.
Remove the onAuthStateChanged just before creating the user, so that it doesn't fire. You can do this by keeping the return value from the call to onAuthStateChanged, which is an unsubscribe function. So something along the lines of:
componentDidMount() {
stopAuthStateListener = firebase.auth().onAuthStateChanged(user => {
...
And then:
if (this.state.role === 'child') {
stopAuthStateListener();
Firebase
.auth ()
.createUserWithEmailAndPassword (email, password)
...
I have a component updating the user's profile pic:
const Updater = () => {
const updateProfilePic = async (photoURL) => {
await auth.currentUser.updateProfile({ 'photoURL': photoURL });
}
}
I have a second component detecting changes in the user's state:
const StateChangesDetector = () => {
auth.onAuthStateChanged( user => {
if(user)
console.log('User changed state', JSON.stringify(user));
});
}
The problem is that auth.onAuthStateChanged() is not triggering after the execution of updateProfile(). Thus, I'm getting the old user's state and the old profile picture.
How can I force to trigger auth.onAuthStateChanged() after updating the user's profile picture?
Try the reload() function on the currentUser object to get updated information. Note that it's asynchronous and returns a promise. If it doesn't trigger the listener (as it's not really a "state" change, just a refresh of data), I suspect you might have to access the firebase.auth().currentUser again after the returned promise resolves to see new data.
I have a react app with which I want to handle authentication with firebase.
My code successfully signs up and logs in but i am trying to add extra information on sign up but i have not been successful. I have tried answers [here]: Firebase v3 updateProfile Method and [here]: Firebase user.updateProfile({...}) not working in React App
But they don't seem to work. Below is my code
const SignUp = ({ history }) => {
const handleSignUp = useCallback(
async event => {
event.preventDefault();
const { email, password } = event.target.elements;
try {
let cred = await app
.auth()
.createUserWithEmailAndPassword(email.value, password.value);
await cred.user.updateProfile({
displayName: 'hello'
});
history.push('/');
} catch (error) {
console.log(error);
}
},
[history]
);
Please how do i fix this because currently on the email and username sets? Thanks
In order to change user profile you should use firebase.auth().onAuthStateChanged() function, as follows:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
Then you can get others user properties. Here you can find all info you need. https://firebase.google.com/docs/auth/web/manage-users. Hope it helps.