简体   繁体   中英

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

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM