52

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.

3
  • Did you try the computed property? Commented Mar 30, 2022 at 11:11
  • Are you sure this is working properly? Did you checked in the Vue devtools? Commented Mar 30, 2022 at 11:26
  • 1
    what do you mean "this is working properly" ? It's not working ;) Commented Mar 30, 2022 at 11:59

2 Answers 2

102

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.

Sign up to request clarification or add additional context in comments.

5 Comments

Any ideas how do I use storeToRefs property within another ref ? i.e. const {price} = storeToRefs(store) const list = ref([{id: 1, price: price}])
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.
but why is it working sometimes?
In my Vue3 App with TS only the following approach worked: import { computed } from 'vue' const themeStore = useThemeStore(); const isDark = computed(() => themeStore.isDark);
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-pinia
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.

1 Comment

Would be great if this worked... but does it? Not reactive for me.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.