So, how do we check for user state changes? Firebase provides an excellent observer function for changes to the user's sign-in state called onAuthStateChanged. To use it, all we need is to import it and provide our app from plugins/firebase.ts
// App.vue
// ...
import { auth } from './plugins/firebase'; // remember to export it first!
import { onAuthStateChanged } from 'firebase/auth';
// ...
onAuthStateChanged(auth, (user) => {
// initially, let it clear the form from the input whenever the state changes
email.value = '';
password.value = '';
});
Thanks to onAuthStateChanged, we now have a place to put our state change handling logic. Additionally, the function we pass is given a user parameter, which is exactly the same object as the user we got from the createUserWithEmailAndPassword() function (the one we saw in the browser’s console).
So now, whenever the user object’s state changes, we can adjust the state of our Vue app accordingly. To save the state after each change, let’s introduce global state management with Pinia to our application. In case you’re interested in best practices of Vue.js Modular state management, we’ve covered this subject in one of our earlier articles.
Meanwhile, let’s create a new store file:
// stores/userStore.ts
import { ref, computed } from 'vue';
import { defineStore } from 'pinia';
import type { User } from 'firebase/auth';
export const useUserStore = defineStore('user', () => {
// here we'll save our user object
const user = ref<User | null>(null);
// if user object exists - we're logged in, and if not - we're not :)
const isLoggedIn = computed<boolean>(() => !!user.value);
// an example state information to display in the app
const userName = computed<string | null>(() => {
if (user.value) {
return user.value?.displayName || user.value?.email
}
return null;
});
// we will use setUser method to save user object on each state change
const setUser = (userData: User | null) => (user.value = userData);
return { user, isLoggedIn, userName, setUser };
});
Now, let’s use the store to save the user information after signing in:
<template>
<main class="content">
<form @submit.prevent class="login-form">
<!-- changing the UI depending on isLoggedIn value -->
<h2 v-if="userStore.isLoggedIn">
<!-- displaying userName to the user - a clear state change sign -->
Welcome, {{ userStore.userName }}
</h2>
<template v-else>
<input v-model="email" type="email" placeholder="Your email..." class="login-input" />
<input
v-model="password"
type="password"
placeholder="Your password..."
class="login-input"
/>
</template>
<div class="buttons">
<button type="submit" @click="signIn" class="submit-button">
Sign in
</button>
<button type="submit" @click="signUp" class="submit-button">
Sign up
</button>
</div>
</form>
</main>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { auth, login, createUser } from './plugins/firebase';
import { useUserStore } from './stores/userStore';
import { onAuthStateChanged } from 'firebase/auth';
// initalizing the store composable
const userStore = useUserStore();
const email = ref<string>('');
const password = ref<string>('');
const signIn = async () => {
if (!email || !password) {
return;
}
await login(email.value, password.value);
};
const signUp = async () => {
if (!email || !password) {
return;
}
await createUser(email.value, password.value);
};
onAuthStateChanged(auth, (user) => {
// saving the user to the store
userStore.setUser(user || null);
email.value = '';
password.value = '';
});
</script>
Let’s break it down:
After typing the user credentials into the form and clicking the SIGN IN button, we will change the user state, which will trigger the onAuthStateChanged observer.
The callback function we pass into the observer will save the user data to the store with the setUser() function.
Thanks to Vue reactivity, the new user state changes all the other computed values from the store, such as isLoggedIn and userName.
We use those values in the App.vue file’s template to change the UI, which, after clicking the SIGN IN button, now looks like this: