Vue Final Project - Part 5 - Storing the 'current' user in the app's GStore
Many of our components will depend on knowing about the user who is currently logged in. It would be very tedious to pass this information around via props! Vue has a 'global storage' mechanism that we can put information into and make it available to any component in the application. This featuer is commonly referred to as GStore, and now will set our app up to use it.
Before we get started, I want to point out that Vue has another way of storing information globally. It's called Vuex, and although it's more commonly used than GStore, it's a lot more complicated to learn. If you go on to do real-world projects with Vue, you'll definitely want to learn more about Vuex.
In main.js, update the import statement that imports createApp from Vue, by adding reactive. The line should now look like this:
import { createApp, reactive } from 'vue'
We are now importing the reactive function from the Vue library, this will allow us to store data that can be accessed by any component (therefore making it 'gobally' available). This data will also be 'reactive' so that when it changes, any component referring to the data will automatically 'react' to reflect the change.
After the import statements in main.js, add this line:
const GStore = reactive({ currentUser: null });
The object being passed into the reactive() function is the data that will be globally available via the GStore constant. Right now the GStore object includes a currentUser property which is set to null. When a user logs in, we'll update the currentUser in our GStore. Then any component in our app will be able to determine who is currently logged in.
Update the last line in main.js to look like this:
createApp(App).use(router).provide('GStore', GStore).mount('#app');
Now the GStore object can be 'provided' to any component in the app.
In order for a component to access to the data in the global store (GStore), we need to 'inject' it into the component. We'll inject it into the Login component, like so (put this line of code inside the options object in Login.vue, just above the data() method):
inject: ['GStore'],
When a user successfully logins in, we'll assign their information to the 'currentUser' key/property of the GStore object, and then we'll redirect the user to the Home view. Update the onSubmit() method in Login.vue to look like this:
onSubmit(){
if(this.validate()){
login(this.email, this.password).then(user => {
if(user){
this.GStore.currentUser = user;
this.$router.push({name: "home"});
}else{
alert("LOGIN FAILED!");
}
})
}
}
If we get a user from the JSON server when we attempt to authenticate, then we set the currentUser property of the GStore to that user. And then we navigate to the Home view (you may want to change where you navigate to depending on your final project, but you can leave it like so for now).
So, for a component to use the GStore, you must inject it into the component. And then you must refer to it as this.GStore when adding code to the options object. But, as you'll see in a moment, you don't include this. when you refer to the GStore from inside the template of a component.
Now we'll make use of the GStore in the App component. In App.vue add a SCRIPT element under the template like so:
<script>
export default {
inject: ['GStore']
}
</script>
Note that we are injecting the global storage (GStore) object into the component.
Add the following code to the template of App.vue, just above the first router-link element:
<span v-if="GStore.currentUser">
<b>Hello {{GStore.currentUser.firstName}} </b>
</span>
So, we have 'injected' the GStore into the App component, and in the template we use the v-if directive to see IF GStore.currentUser is truthy. If so, then we display a Hello message to the user who is currently logged in.
Run the app and login with valid user credentials. You should get redirected to the Home component, where you'll see the users firstName displayed.
Unfortunately, when you refresh the page the GStore's currentUser property gets reset to it's default value of null, which will force you to login again.
In order to keep the user logged in during page refreshes, we can store the currentUser in the browser's sessionStorage. The sessionStorage object is very similar to localStorage, which you are already familiar with. Please ask me about the difference between the two in class.
In Login.vue update the call to login() to look like this (I've just added one line):
login(this.email, this.password).then(user => {
if(user){
this.GStore.currentUser = user;
// ADD THIS LINE:
sessionStorage.setItem("currentUser", JSON.stringify(user));
this.$router.push({name: "Home"});
}else{
alert("LOGIN FAILED!");
}
})
So now, when a user logs in, we can save their information in the browser's session storage, by setting a currentUser key. And when the app loads (for example during a page refresh), we'll check the session storage to see if the key exists and has data is in it.
In main.js replace this line:
const GStore = reactive({ currentUser: null });
with this code:
const userJSON = sessionStorage.getItem("currentUser")
const user = userJSON ? JSON.parse(userJSON) : null;
const GStore = reactive({ currentUser: user });
Please let me know if you have questions about this code. The second line is setting the user constant via the ternary operator to either 'parse' the userJSON string (if it is truthy) or set it to null.
Now run the app, login, then refresh the page. You should remain logged in.
Note that we'll have to adjust this code when we use this app with a real back-end, but for now it will work like this while we are using the JSON server.
Now we'll add functionality to log out.
In App.vue, update the last router-link element to use a v-if directive like so:
<router-link v-if="!GStore.currentUser" :to="{name: 'Login'}">Login</router-link>
This will only display the router-link if the user has NOT logged in.
Add this just below the router-link element that you modified:
<a v-else @click="logout" href="#">Log Out</a>
Notice how it's using the v-else directive, which works with the v-if directive on the previous line.
You might be wondering why we're not using a router-link element, and it's because we will not be navigating to another component. Instead, when the link is clicked, we'll invoke a method called logout(), which we'll add to the App component next.
Finally, add the logout() method to the SCRIPT element in App.vue (just under the line that injects the GStore - don't forget to add a comma before pasting the code):
methods:{
logout(evt){
evt.preventDefault();
this.GStore.currentUser = null;
sessionStorage.removeItem("currentUser");
this.$router.push({name:"Login"});
}
}
This will reset the currentUser in the GStore to null, and remove the currentUser key from sessionStorage. It will then redirect back to the Login component.
Run the app and try it out (login, refresh, logout - keep an eye on how the NAV element behaves).
Adapting the Navigation Based on User Role
Only admins are allowed to view, add, and edit users. So we'll hide the router-link that says 'Users' unless the current user is an admin.
In App.vue, update the router-link for Users to look like this:
<router-link :to="{name: 'UserList'}" v-if="GStore.currentUser?.roleId == 2">
Users
</router-link>
Notice the use of the optional chaining operator (the ? after 'currentUser'). If the user has not logged in then GStore.currentUser will be null, and if we didn't include the ? (the optional chaining operator), we'd get an error saying that NULL does not have a roleId property.
Note that I'm not a big fan of using the 'magic number' 2 (which is the roleId for admins), but it works for now. Feel free to ask me about this in class.
Log in as an Admin, and you should see the link to Users. Then log out and log back in as a Standard User, and you should not see the link.
The nav bar will look a little funny because of the pipes (|), but we'll take care of this when we Bootstrap the nav bar together.
Note you can still enter "/users" into the URL bar and get to the Users page. There are a few ways that we could prevent this, and we'll deal with the issue when we move away from the JSON server and use a real back-end.
Now is a good time to Bootstrap the App.vue compoment. But the navigation can be a little bit tricky (I discovered a strange issue when I did it). So please ask me about doing this step together in class.