3

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>
2
  • Because we need to have a getter as explained in the docs. This is mostly a shortcut that we use with ES6 to make it shorter. I guess it's the way to go because Vue uses proxies and we need to evaluate them in the computed, rather than just pass a variable to it. Not an expert on it, more people might give detailed info. Commented May 17, 2024 at 3:03
  • 1
    @kissu A getter won't serve a good purpose because it would have the same structure as "user" and behave the same way when destructured Commented May 17, 2024 at 8:31

1 Answer 1

3

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);
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks! Here's the relevant section in the docs.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.