auth.onAuthStateChanged not triggering after auth.currentUser.updateProfile in Firebase - javascript

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.

Related

call api and render data after states get updated

In use effect I am checking current role of user, if it is admin setAdmin(true) set the state of admin. The default admin state is false. When component gets rendered, it gets rendered with admin as false and state gets updated in sometime. I want to call api and render data after state gets updated.
useEffect(async () => {
//Authenticates User through their email
let firebaseId;
const user = await firebase.auth().currentUser;
if (user) {
let getUserProfile = async () => {
let loggedInUser = await axios.get(
`${config.API_URL}/data/profile/${user.uid}`
);
if (loggedInUser.data.role == "admin") {
setAdmin(true);
}
};
getUserProfile();
}
}, []);
You can use useEffect hook in above case.
Ex:
useEffect(() => {
// Call your API
}, [admin]);
Above hook will call when admin state updated.

Not getting data from firebase on opening the app

I am trying to get data from firebase but it returns empty value when the app loads, but if I edit something on that file even the commented line, then the data loads and app runs, I want when the app opens all data should be there from firebase to run app. and also how to arrange "grabbedData" in reverse order tried grabbedData.reverse() but doent work.
const Getdata = async () => {
let grabbedData = [];
await firebase
.database()
.ref(`/users/`)
.orderByKey()
.on("value", (snapshot, key) => {
// console.log("snapshot....", snapshot);
grabbedData.push(snapshot.val());
});
setUserdata(grabbedData);
console.log("grabbedData", grabbedData); // empty value here :(
if (grabbedData) {
let getfranchiseuid = "";
Object.keys(grabbedData).map(function (key) {
let y = grabbedData[key];
Object.keys(y).map(function (key2) {
let x = y[key2];
if (key2 === uid) {
getfranchiseuid = x.franchiseuid;
}
});
});
if (getfranchiseuid) {
let customerList1 = [];
firebase
.database()
.ref(`/serviceProvider/${getfranchiseuid}/franchise/customers`)
.orderByKey()
.on("value", (snapshot) => {
customerList1.push(snapshot.val());
});
setCustomerList(customerList1);
console.log("customerList1customerList1", customerList1);
}
}
};
useEffect(() => {
var unsubscribe = firebase.auth().onAuthStateChanged(function (user) {
if (user) {
storeUser({ user });
setUser(user);
setEmail(user.email);
setUid(user.uid);
} else {
// No user is signed in.
}
});
unsubscribe();
Getdata();
}, []);
Data is loaded from Firebase asynchronously. Since this may take some time, your main JavaScript code will continue to run, so that the user can continue to use the app while the data is loading. Then when the data is available, your callback is invoked with that data.
What this means in your code is that (for example) right now your setUserdata is called before the grabbedData.push(snapshot.val()) has run, so you're setting any empty user data. You can most easily see this by setting some breakpoints on the code and running it in a debugger, or by adding logging and checking the order of its output.
console.log("1");
await firebase
.database()
.ref(`/users/`)
.orderByKey()
.on("value", (snapshot, key) => {
console.log("2");
});
console.log("3");
When you run this code, the output will be:
1
3
2
This is probably not what you expected, but it is exactly correct and does explain your problems.
The solution for this is always the same: any code that needs the data from the database must be inside the callback, or be called from there.
So for example:
await firebase
.database()
.ref(`/users/`)
.orderByKey()
.on("value", (snapshot, key) => {
grabbedData.push(snapshot.val());
setUserdata(grabbedData);
});
this will ensure that setUserdata is called whenever you updated the grabbedData.
Since you have much more code that depends on grabbedData, that will also have to be inside the callback. So the entire if (grabbedData) { block will need to be moved, and probably others. If you keep applying the solution above, the code will start working.
This is a very common problem for developers that are new to calling asynchronous cloud APIs, so I highly recommend reading some of these other answers:
Why Does Firebase Lose Reference outside the once() Function?
Best way to retrieve Firebase data and return it, or an alternative way
How do I return the response from an asynchronous call? (this one is not specific to Firebase, as the problem is not specific to Firebase)

How can I cancel a state change from a resolving promise after component is already unmounted?

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("/");
}

Firebase, user registration and navigation issue

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)
...

Firestore snapshot not updating properly

I was wondering if a Firestore document snapshot is being refreshed if the function runs again.
Here I am trying to get a readout of the 'active' property which returns a boolean.
Unfortunately, if I manually change the value in the document to 'false', the readout still shows 'true', even after reloading (and supposedly re-running the function).
// Check if user is logged in
auth.onAuthStateChanged(user => {
if (user) {
findSubscription(user);
} else {
console.log('logged out')
}
})
const findSubscription = (user) => {
// Get document
db.collection('stripe_customers')
.doc(user.uid)
.collection('subscription_info')
.doc('subscription_object')
.get()
.then((doc) => {
// Get number of keys in document
const numberOfKeys = Object.keys(doc.data()).length;
// Check if numverOfKeys is > 0 (it is not if the user has never had a subscription before)
if (numberOfKeys !== 0) {
// Get subscription status
const subscriptionStatus = doc.data().subscription.plan.active;
console.log(subscriptionStatus);
Occasionally I am getting the following console log.
"XMLHttpRequest cannot load ('google api url') due to access control checks."
My database is still in public mode for testing so I am not quite sure what that is all about.
You need to call onSnapshot() instead of get() (https://firebase.google.com/docs/firestore/query-data/listen)
let unsubscribe = null;
const addSubscription = (user) => {
// Subscribe to document updates
// (called immediately & every subsequent change)
unsubscribe = db.collection('stripe_customers')
.doc(user.uid)
.collection('subscription_info')
.doc('subscription_object')
.onSnapshot((doc) => { ... })
}

Categories

Resources