vue component doesn't update after state changes in pinia store
Asked Answered
N

3

44

I'm currently working on my first vue application, currently building the login logics. For State management, pinia is being used. I created a Pinia Store to manage the "isLoggedIn" state globally.

import { defineStore } from "pinia";

export const useLoginStatusStore = defineStore('loginStatus', {
    id: 'loginStatus',
    state: () => ({
        isLoggedIn: false
    }),
    actions: {
        logIn() {
            this.isLoggedIn = true
            console.log("Login", this.isLoggedIn)
        },
        logOut() {
            this.isLoggedIn = false
            console.log("Logout", this.isLoggedIn)
        }
    }
})

So far so good, its working, i can access the state and actions in the components and router file.

**<roouter.js>**

import { createRouter, createWebHistory } from 'vue-router'
import { createPinia } from 'pinia'
import { createApp, ref } from 'vue'
import { useLoginStatusStore } from '../stores/loginStatus.js'

import App from '../App.vue'
import WelcomeView from '../views/public/WelcomeView.vue'
import SplashView from '../views/public/SplashView.vue'

const pinia = createPinia()
const app = createApp(App)
app.use(pinia)

const loginStatusStore = useLoginStatusStore()
let isLoggedIn = ref(loginStatusStore.isLoggedIn)

console.log("isLoggedIn", loginStatusStore.isLoggedIn)


const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'splash',
      component: SplashView
    },
    {
      path: '/welcome',
      name: 'welcome',
      component: WelcomeView
    },
    {
      path: '/login',
      name: 'login',
      component: () => import('../views/public/LoginView.vue')
    },
    {
      path: '/signup',
      name: 'signup',
      component: () => import('../views/public/SignUpView.vue')
    },
    {
      path: '/resetpassword',
      name: 'resetpassword',
      component: () => import('../views/public/ForgotPasswordView.vue')
    },
    {
      path: '/home',
      name: 'home',
      component: () => import('../views/protected/HomeView.vue'),
      meta: { requiresAuth: true }
    },
    {
      path: '/sounds',
      name: 'sounds',
      component: () => import('../views/protected/SoundsView.vue'),
      meta: { requiresAuth: true }
    },
    {
      path: '/player',
      name: 'soundPlayer',
      component: () => import('../views/protected/SoundPlayerView.vue'),
      meta: { requiresAuth: true }
    },
    {
      path: '/profile',
      name: 'profile',
      component: () => import('../views/protected/ProfileView.vue'),
      meta: { requiresAuth: true }
    },
    {
      path: '/meditation',
      name: 'meditation',
      component: () => import('../views/protected/MeditationView.vue'),
      meta: { requiresAuth: true }
    },
    {
      path: '/tools',
      name: 'tools',
      component: () => import('../views/protected/ToolsView.vue'),
      meta: { requiresAuth: true }
    }
  ]
})

router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth) {
    console.log("Router", isLoggedIn.value)
    if (!isLoggedIn.value) {
      next({
        name: 'welcome'
      })
    } else {
      next()
    }
  } else {
    next()
  }
})

export default router

In the router it's being used for protected routes and in App.vue for conditional class rendering.

The Problem is, that when the state gets updated, it doesn't get updated in the components and the components themselves don't update either. I tried with the $subscribe method in pinia, but didnt manage to get it working. I know, whats needed is something that creates reactivity here. But no clue how to do that. I'm grateful for any help with this :)

thanks for reading

**App.vue**

<script setup>
import { RouterView } from 'vue-router';
import DevNavItem from '@/components/header/DevNavItem.vue'
import HeaderItem from '@/components/header/HeaderItem.vue'
import FooterItem from '@/components/footer/FooterItem.vue'
import { useLoginStatusStore } from './stores/loginStatus.js';

const loginStatusStore = useLoginStatusStore()
const isLoggedIn = loginStatusStore.isLoggedIn

console.log("App.vue", loginStatusStore.isLoggedIn)

</script>

<template>
  <DevNavItem />
  <HeaderItem v-if="isLoggedIn" />
  <RouterView :class="isLoggedIn ? 'mainProtected' : 'mainPublic'" />
  <FooterItem v-if="isLoggedIn" />
</template>

<style>
/*FONT-IMPORT*/
@import url("@/assets/font/alegreya_font.scss");

/* GENERAL STYLES */

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}
header {
  position: top;
}
.mainProtected {
  width: 100vw;
  height: 83vh;
  overflow: hidden;
}
.mainPublic {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}

/* GLOBAL CLASSES */

.mainLogo {
  height: 350px;
  width: 350px;
  background: url("./img/icons/main.png") center/cover no-repeat;
}
.leavesBackground {
  background-color: #253334;
  background-image: url("./src/img/images/background_partial.png");
  background-repeat: no-repeat;
  background-position: bottom;
  background-size: contain;
}
.logoSmall {
  background: url("./img/icons/main.png") center/contain no-repeat;
  height: 100px;
  width: 100px;
}
.buttonPublic {
  padding: 20px 0;
  text-align: center;
  background-color: #7c9a92;
  color: #fff;
  border-radius: 15px;
  width: 90%;
  text-decoration: none;
  font-size: 24px;
  border: none;
}
</style>

I tried subscribing to the state with $subscribe, but it didn't work.

Nephritic answered 30/3, 2022 at 11:7 Comment(3)
Did you try the computed property?Torrens
Are you sure this is working properly? Did you checked in the Vue devtools?Freelance
what do you mean "this is working properly" ? It's not working ;)Seclusion
N
92

storeToRefs()

You need to use storeToRefs() to extract properties from the store while keeping those properties reactive.

import { storeToRefs } from 'pinia'
const themeStore = useThemeStore();
const { isDark } = storeToRefs(themeStore);

Computed property

Thanks to @Fennec for suggesting the computed way of getting reactive state. Although I don't recommend this method since there is a dedicated storeToRefs() available.

import { computed } from 'vue'
const themeStore = useThemeStore();
const isDark = computed(() => themeStore.isDark);

WRONG ways to get reactive state from the Pinia store:

All the ways listed below of getting the state (properties, getters) from the Pinia store are WRONG:

import { useThemeStore } from "./stores/theme.js";
const themeStore = useThemeStore();

// WRONG ways of extracting state from store
let isDark = themeStore.isDark; // not reactive
let isDark = ref(themeStore.isDark); // reactive, but will not be updated with the store
let { isDark } = themeStore; // not reactive, cannot destructure

Destructuring actions directly from the store.

Its worth to note here that "you can destructure actions directly from the store as they are bound to the store itself." (docs)

If you have an action named "increment" in your store, you can just extract it directly from the store in your component:

...
const { increment } = store // actions can be destructured directly
...

Also, according to Pinia docs, the first argument is the unique ID, so you do not need to specify the id again inside the options object. Or you can just ignore the first argument and just specify the id as an option. Either way is fine.

Nye answered 30/3, 2022 at 12:14 Comment(5)
Any ideas how do I use storeToRefs property within another ref ? i.e. const {price} = storeToRefs(store) const list = ref([{id: 1, price: price}])Correlative
That will not work, the price will be reactive and will update but the list will not be updated when price changes. In this case, you have to make your list a computed property.Nye
but why is it working sometimes?Peppie
In my Vue3 App with TS only the following approach worked: import { computed } from 'vue' const themeStore = useThemeStore(); const isDark = computed(() => themeStore.isDark);Backfill
Per Eduardo San Martin Morote himself "Extra Tip: most of the time you don't need storeToRefs() (or toRef()) either. You can just use the store directly! Vue reactivity is really convenient " source:: masteringpinia.com/blog/my-top-5-tips-for-using-piniaHett
C
1

one way is to create a method in store that returns the desired state.

function getDesiredState() {
 return desiredState;
}

it is reactive without using storeToRefs.

Carvelbuilt answered 1/8, 2023 at 9:11 Comment(1)
Would be great if this worked... but does it? Not reactive for me.Ez
E
-3
                                    PiniaVuePlugin

you have to ensure you import PiniaVuePlugin and use it in main.js file:

import { createPinia, PiniaVuePlugin } from 'pinia'

Vue.use(PiniaVuePlugin)
const pinia = createPinia();

new Vue({
render: (h) => h(App),
pinia,
}).$mount("#app");
Eraste answered 27/6, 2023 at 14:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.