Here is the summary of changes after all refactors and researches
Appreciate for help: @shivamyadav, @shahriar_shaon and StackOverflow
Key points to pay attention to
-
there's no reason to keep setUser and getCurrentUser separate from the store, a store is not just a global state but can contain related business logic.
-
there's supposed to be a global auth guard instead of per route beforeEnter, this is where logic takes place:
router.beforeEach(async (to, from) => {
if (from === START_LOCATION) {
const userStore = useUserStore();
await userStore.getUser();
}
})
-
any initialization logic can be run like that. It's convenient to declaratively mark routes that need auth with meta: { auth: true } with this guard and run auth logic only if to.meta.auth === true.
-
it needs to be taken into account that initial navigation starts before an application is mounted. In order to avoid possible race conditions, it can be:
await router.isReady()
app.mount('#app')
Code
main.js
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import router from './router';
import './style.css';
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.use(router)
await router.isReady()
app.mount('#app')
router/index.js
import { createRouter, createWebHistory, START_LOCATION } from 'vue-router'
import LoginView from '@/views/auth/LoginView.vue'
import RegisterView from '@/views/auth/RegisterView.vue'
import ToursView from '@/views/ToursView.vue'
import { useUserStore } from '@/store/user.js'
const routes = [
{
name: 'Login',
path: '/login',
component: LoginView,
meta: { auth: false }, // for Unauthenticated users
},
{
name: 'Register',
path: '/register',
component: RegisterView,
meta: { auth: false }, // for Unauthenticated users
},
{
name: 'Tours',
path: '/tours',
component: ToursView,
meta: { auth: true }, // for Authenticated users
},
// ...
];
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore();
if (from === START_LOCATION) {
await userStore.getUser();
}
if ((to.name === 'Login' || to.name === 'Register') && userStore.isAuthorized) {
return next('/')
}
if (to.meta.auth && !userStore.isAuthorized) {
return next('/login')
}
next()
})
export async function routerPush(name, params) {
if (params === undefined) {
return await router.push({ name })
} else {
return await router.push({ name, params })
}
}
export default router
store/user.js
import { computed, ref } from 'vue'
import { defineStore } from 'pinia'
import { getCurrentUser } from '@/api/user.js'
export const useUserStore = defineStore('user', () => {
const user = ref(null)
const isAuthorized = computed(() => !!user.value)
async function getUser() {
try {
const userData = await getCurrentUser()
userData ? user.value = userData : user.value = null
console.log('Logged in: ', user.value?.name)
} catch (error) {
console.log('Error: ', error)
return error
}
}
function updateUser(userData) {
userData ? user.value = userData : user.value = null
}
return {
user,
isAuthorized,
getUser,
updateUser
}
})
Usage
For example here is how my LoginView.vue script looks like
LoginView.vue
<script setup>
import GuestLayout from '@/layouts/GuestLayout.vue'
import { reactive, ref } from 'vue'
import { login } from '@/api/auth.js'
import { routerPush } from '@/router/index.js'
import { useUserStore } from '@/store/user.js'
const form = reactive({
email: '',
password: '',
})
const { updateUser } = useUserStore()
const errorMessage = ref('')
async function onLogin(form) {
errorMessage.value = ''
try {
const userData = await login(form)
updateUser(userData)
await routerPush('Dashboard')
}
catch (error) {
errorMessage.value =
error.response?.data?.message || 'Something went wrong'
}
}
</script>