I am storing an authenticated user of my web app (Vue and Supabase) in a Pinia store: userStore.user
. My NavBar component has a login/logout link based on this state. In order to make this reactive within my NavBar I need to make user
a computed property: const user = computed(() => userStore.user)
.
Why does user
in my NavBar need to be a computed property? I thought it would be reactive simply by pointing to user in userStore, since this is reactive within the Pinia store. Also, wrapping it in a ref did not make it reactive. I'm new to Vue and Pinia, and obviously missing some key concept.
Here's my code in context:
// UserStore.js
import { defineStore } from 'pinia'
import { supabase } from '@/supabase'
export const useUserStore = defineStore('user', {
state: () => ({
user: null,
isLoggingIn: false,
isLoggingOut: false
}),
actions: {
async fetchUser() {
const {
data: { session }
} = await supabase.auth.getSession()
this.user = session?.user || null
},
async login(email, password) {
this.isLoggingIn = true
try {
const { user, error } = await supabase.auth.signInWithPassword({ email, password })
if (error) throw error
this.user = user
} finally {
this.isLoggingIn = false
}
},
async logout() {
this.isLoggingOut = true
try {
await supabase.auth.signOut()
this.user = null
} finally {
this.isLoggingOut = false
}
},
setUser(user) {
this.user = user
}
}
})
// NavBar.vue
<template>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/restricted">Restricted</router-link> |
<router-link v-if="!user" to="/login">Login</router-link>
<button v-else @click="logout" :disabled="isLoggingOut">
{{ isLoggingOut ? 'Logging out ...' : 'Logout' }}
</button>
</nav>
</template>
<script setup>
import { computed, ref } from 'vue'
import { useUserStore } from '@/stores/UserStore'
const userStore = useUserStore()
// const user = userStore.user // Not reactive to changes in userStore - why?
// const user = ref(userStore.user) // Also not reactive - why?
const user = computed(() => userStore.user) // This works
const isLoggingOut = computed(() => userStore.isLoggingOut)
const logout = userStore.logout
</script>
There is no way how it could maintain reactivity in JavaScript.
This is what happens here:
const foo = { bar: { baz: 'baz' } };
const barCopy = foo.bar;
foo.bar = { qux: 'qux' };
console.log(barCopy) // baz: 'baz'
This could work if existing user
object were never reassigned but its properties were mutated instead, but in your case it's completely reassigned, which is a reasonable; it's not an object but null
initially.
In order to maintain reactivity, it should be consistently used as userStore.user
in order to maintain the reference, this includes using it inside computed
. Pinia's helper allows to create a writable computed in less verbose way:
const { user } = storeToRefs(userStore);