Vuefire: Synchronous firebase.auth().currentUser replaced with Asynchronous onAuthStateChanged - causing 'not defined' error.

Created on 21 Jun 2016  路  10Comments  路  Source: vuejs/vuefire

Hey,

Firstly, thanks for the hard work in getting Vuefire 1.1.0 out.

In the Firebase 2.x.x SDK we had the synchronous getAuth() which returned authorisation and user information.

In the 3.x SDK this has been replaced with firebase.auth().onAuthStateChanged which is asynchronous (there's also firebase.auth().currentUser but that returns null when the auth object has not finished initialising which seems to take a long time).

If you have a Firebase path that depend on the users UID, for example:

userLists = firebase.database().ref('/lists/' + userUID);

and in Vue:

firebase: {
        lists: userLists
}

The code now return a 'userLists not defined' error, as we must wait for firebase.auth().onAuthStateChanged to return the user object containing the UID.

Is there any best way to deal with this, where Vue waits for the object to be returned or something similar?

I tried a ternary operator on userLists, but got either a 'VueFire: invalid Firebase binding source' or 'source.on is not a function' error.

Cheers.

question

Most helpful comment

@weislanes I will tell you how I managed to get the users auth state, as @jwngr said, im using vue-router:

The userAuth action file:

import 'firebase/auth';
import firebaseApp from 'src/config/firebase';

const getUserStatus = function (store) {
  store.dispatch('CHECK_USER_STATUS');

  return new Promise(function (resolve, reject) {
    firebaseApp.auth().onAuthStateChanged(function (user) {
      if (user) {
        store.dispatch('LOGIN_SUCCESS', user.uid);
        resolve(user.uid);
      } else {
        store.dispatch('LOGIN_FAIL');
        reject(Error('It broke'));
      }
    });
  });
};

export { getUserStatus };

Then in your component file import the action and wait for the data in with the router:

...
import { getUserStatus } from 'actions/auth/getUserStatus';

export default {
  name: 'Home',
  route: {
    // waitForData: true,
    data: function (transition) {
      return this.getUserStatus()
        .then(function (uid) {
          return { uid: uid };
        })

        .catch(function (err) {
          transition.abort(err);
        });
    }
  },
....

and in your templante you can put:

<div v-if="$loadingRouteData">Loading...</div>
<div v-if="!$loadingRouteData" class="col-sm-6 col-md-3">
    <!-- your component with loaded data -->
</div>

All 10 comments

@yyx990803 will hopefully be able to answer this with more specifics than myself, but in general, you'd want to use a router to (1) verify that a user is logged in and (2) get that user's uid. Then, once you've got that, you'd want to initialize your binding. Are you using a router? Can you share some more code of what you have tried?

@weislanes I will tell you how I managed to get the users auth state, as @jwngr said, im using vue-router:

The userAuth action file:

import 'firebase/auth';
import firebaseApp from 'src/config/firebase';

const getUserStatus = function (store) {
  store.dispatch('CHECK_USER_STATUS');

  return new Promise(function (resolve, reject) {
    firebaseApp.auth().onAuthStateChanged(function (user) {
      if (user) {
        store.dispatch('LOGIN_SUCCESS', user.uid);
        resolve(user.uid);
      } else {
        store.dispatch('LOGIN_FAIL');
        reject(Error('It broke'));
      }
    });
  });
};

export { getUserStatus };

Then in your component file import the action and wait for the data in with the router:

...
import { getUserStatus } from 'actions/auth/getUserStatus';

export default {
  name: 'Home',
  route: {
    // waitForData: true,
    data: function (transition) {
      return this.getUserStatus()
        .then(function (uid) {
          return { uid: uid };
        })

        .catch(function (err) {
          transition.abort(err);
        });
    }
  },
....

and in your templante you can put:

<div v-if="$loadingRouteData">Loading...</div>
<div v-if="!$loadingRouteData" class="col-sm-6 col-md-3">
    <!-- your component with loaded data -->
</div>

@jwngr, @xDae thanks for the replies. Will try this now.

@weislanes You can also dynamically bind array and object with $bindAsArray and $bindAsObject

@jwngr Should we be closing this?

I tried this technique and it works fairly well in a "login" page. However after I get redirected after the FB or Google login on to the same page, the current user is still null. I have to refresh the page to make it work. Any idea why?

@kojilab - Please open up a new issue with a repro. Note that you may need to wait for onAuthStateChanged() to fire before currentUser will be populated.

I found a way of getting round this...

context / the problem:
i was firing off firebase.auth().onAuthStateChange() - this takes an argument (a callback).
If you treat it like a promise and .then() it, it fails.
If you await on it, it also fails.
Your options seem limited to _give it a callback that then updates a global variable when it completes_ OR have a cry.
I thought firebase methods were setup to be promises or callbacks, but fine whatever.

After a day of convincing myself i had misunderstood the fundamentals of Async Await or something...

i then looked at firebase.auth().currentUser - this helped a _bit_.
But if i clicked my <button onClick={this.checkLoginStatus} /> to fire off my checkLoginStatus() on the React side - it would only work the second time. Of course that's down to asynchronous behaviour. But i couldn't understand what _specifically_ was causing it. If i stripped out all the async await and promise syntax, went back to just callback - my code looked like either:-

util function 1:

checkLoginStatus = async() => {
   let user = await firebase.auth().currentUser;
   console.log('user');
}

or util function2:

checkLoginStatus = () => {
   let user = null;
   firebase.auth().onAuthStateChanged((fbUser) => {
         if (fbUser && fbUser.email) {
             user = fbUser
               console.log('fbUser is: ', user);
         } else {
             user = null;
          }
   })
   console.log('user is: ', user);
}

neither seemed to work. I'd always get false/null/undefined responses.

then in React, for async-await efforts i had:-

checkLoginStatusReactSide = async () => {

        try {
            let user = await checkLoginStatus(); // pulled in from utils.js
            console.log('shows as null the first time: ', user);
            if (user && user.email) {
                this.setState({
                    isLoggedIn: true, // <-- bounces me through to the Homepage.
                });
                console.log('user is found on second click: ', user.email);
            }
        } catch(err) {
            console.log('catch | error: ', err);   
        }
    }

SOLUTION :
Finally i ended up not only _wrapping_ the onAuthStateChanged() method in a promise, but recursively calling itself, so it's resolving with an actual user response.

The only way it would reject is if the actual API failed.

Just tested and this seems to work okay. So now the utils function looks like this:-

export function checkLoginStatus() {
    return new Promise((resolve, reject) => {
        const unsubscribe = firebase.auth().onAuthStateChanged(user => {
            unsubscribe();
            resolve(user);
        }, reject('api failed'));
    });
}

React looks like this:

    checkLoginStatusReactSide = async () => {

        try {
            let user = await checkLoginStatus();
            console.log('try | react side: ', user); 
            if (user && user.email) {
                this.setState({
                    isLoggedIn: true,
                });
                console.log('user is found: ', user.email);
            }
        } catch(err) {
            console.log('catch | error: ', err);   
        }
    }

I hope that saves someone the 2 days i've been stuck on this.

Awkward Hugs,

Aid

Just want to share my solution to this that seems to work in a Vue cli app.
I tried @Aid19801 's solution but it only almost got me there. It was rejecting and THEN resolving, which was weird.

So, my solution is to have a method on the Vue component:

checkAuthStatus() {
    return new Promise((resolve, reject) => {
        try {
          firebase.auth()
           .onAuthStateChanged(user => {
               console.log('userChecked:', user)
               resolve(user);
           });
        } catch {
          reject('api failed')
        }
      });
}

Which I can then await for in my mounted call:

let user = await this.checkAuthStatus()

Just make sure to set your mounted hook to be async

Seems to be working for me so far and has gotten me out of a 5-day pickle (part-time, however).

Even more awkward hugs

Just want to share my solution to this that seems to work in a Vue cli app.
I tried @Aid19801 's solution but it only almost got me there. It was rejecting and THEN resolving, which was weird.

So, my solution is to have a method on the Vue component:

checkAuthStatus() {
      console.log("running checkAuthStatus()")

       return new Promise((resolve, reject) => {
        try {
          firebase.auth()
           .onAuthStateChanged(user => {
               console.log('userChecked:', user)
               resolve(user);
           });
        } catch {
          reject('api failed')
        }
      });

    },

Which I can then await for in my mounted call:

let user = await this.checkAuthStatus()

Just make sure to set your mounted hook to be async

Seems to be working for me so far and has gotten me out of a 5-day pickly (part-time, however).

Even more awkward hugs

This works perfectly for me... slightly cleaned-up version:

function checkAuthStatus() {
  return new Promise( (resolve, reject) => {
    try {
      firebase.auth().onAuthStateChanged(user => resolve(user))
    } catch(err) {
      reject(err)
    }
  })
}

@Chippd that is perfect solution. I had same situation. (_It was rejecting and THEN resolving_)
thank u very much.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

drumanagh picture drumanagh  路  3Comments

RobertAKARobin picture RobertAKARobin  路  4Comments

KMolendijk picture KMolendijk  路  3Comments

joelxr picture joelxr  路  3Comments

JFGHT picture JFGHT  路  5Comments