Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

persisting pinia store within Quasar app #46

Closed
BenJackGill opened this issue Mar 9, 2022 · 12 comments
Closed

persisting pinia store within Quasar app #46

BenJackGill opened this issue Mar 9, 2022 · 12 comments
Labels
✨ enhancement New feature or request ✋ help wanted Extra attention is needed

Comments

@BenJackGill
Copy link

BenJackGill commented Mar 9, 2022

I'm trying to use this Pinia Plugin in my Quasar app (Vue 3 / TypeScript).

Out of the box everything works fine.

But when using a Quasar boot file the persisted state stops working. Refreshing the page wipes all the new values away.

I don't know why the boot file breaks the persisted state plugin, but I have narrowed the culprit down to a single line...

This is how I am using Pinia with Quasar and adding the plugin:

src/store/index.ts

/* eslint-disable @typescript-eslint/no-unused-vars */
import { store } from 'quasar/wrappers';
import { createPinia, Pinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';

declare module '@quasar/app' {
  interface BootFileParams<TState> {
    store: Pinia;
  }
  interface PreFetchOptions<TState> {
    store: Pinia;
  }
}

declare module '@vue/runtime-core' {
  interface ComponentCustomProperties {
    $store: import('pinia').Pinia;
  }
}

export default store(function (_) {
  const pinia = createPinia();
  pinia.use(piniaPluginPersistedstate); // Pinia Plugin added here
  return pinia;
});

And this is what my Pinia store looks like:

src/store/user.ts

import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
  state: () => {
    return {
      user: {
        firstName: 'Mary',
      },
    };
  },
  persist: true, // Note that we are using a persisted state here
  actions: {
    setFirstName(firstName: string) {
      this.user.firstName = firstName;
      console.log('Name set to Pinia store: ', this.user.firstName);
    },
    getFirstName() {
      if (!this.user.firstName) {
        console.log('No name found in store. Setting "John" to Pinia store.');
        this.user.firstName = 'John';
        return this.user.firstName;
      } else {
        console.log('Name fetched from Pinia store: ', this.user.firstName);
        return this.user.firstName;
      }
    },
  },
});

Here is an example front-end page for fetching and setting the firstName:

src/pages/index.vue

<template>
  <div>{{ firstName }}</div>
  <q-form @submit="handleFirstNameSubmit">
    <p>Change First Name</p>
    <q-input v-model="firstNameInput" filled outline />
    <q-btn label="Submit Name to Pinia Store" type="submit" />
  </q-form>
  <q-btn @click="handleFirstNameFetch" label="Fetch Name from Pinia Store" />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useUserStore } from 'src/store/user';
const userStore = useUserStore();
const firstName = ref<string>();
const firstNameInput = ref<string>();
const handleFirstNameSubmit = () => {
  if (firstNameInput.value) {
    userStore.setFirstName(firstNameInput.value);
  }
};
const handleFirstNameFetch = () => {
  firstName.value = userStore.getFirstName();
};
</script>

Up to this point everything works fine.

I can set firstName to the Pinia store, refresh the page, and the new name is still in Pinia.

But when trying to use const userStore = useUserStore(store) inside a boot file like the example below, the persisted state stops working:

src/boot/auth.ts

import { boot } from 'quasar/wrappers';
import { useUserStore } from 'src/store/user';

export default boot(({ store }) => {
  const userStore = useUserStore(store);
  // Do some other authentication stuff, setting initial user store values etc, below here...
});

Any idea what's going on? And how to fix it?

I think this plugin is much cleaner than using the alternate LocalStorage persisted state solution so I would love to get it working with Quasar.

@prazdevs prazdevs changed the title I cannot make Pinia persist state upon page refresh persisting pinia store within Quasar app Mar 9, 2022
@prazdevs
Copy link
Owner

prazdevs commented Mar 9, 2022

I dont really see what could be wrong, but I played a bit with the reprod repo, and in the vue devtools, there is no event related to pinia that is fired. This is weird and I dont really understand why, but that could be related to the issue since we subscribe to state mutations (like the devtools). My guess is there is smth with quasar, and i never used it so I can't help much atm. I'll try to look further, or if anyone has an idea.

@prazdevs prazdevs added the ✋ help wanted Extra attention is needed label Mar 9, 2022
@BenJackGill
Copy link
Author

BenJackGill commented Mar 10, 2022

Ok thanks, you have set me on the right path because I have narrowed it down to being a problem with my Quasar boot file.

Also I realised the opening post wasn't very clear, so I have updated it to reflect the current situation.

Will post an update here if I find one!

@yusufkandemir
Copy link

Hi, Quasar core team member here 👋

It's about Pinia's limitation about using plugins before the app is initialized:
quasarframework/quasar#12736 (comment)

We will release first-party Pinia support soon, and mention this limitation on our docs, pointing to the Pinia documentation.

@yusufkandemir
Copy link

Quasar Framework has first-party Pinia support since @quasar/app-vite-v1.0.0-beta.1 and @quasar/app-webpack-v3.4.0.

This limitation is by-passed by passing the store to the app instance earlier since @quasar/app-vite-v1.0.0-beta.3 and @quasar/app-webpack-v3.4.3.

So, I think this issue can be closed now. But, I think something like createQuasarPersistedState that uses LocalStorage/SessionStorage and/or Cookies plugins would be really cool. It can be supported with a boot file example(similar to Nuxt's plugins). These great samples from @TobyMosque might be used as a reference implementation:

The boot file that contains an alternative implementation to pinia-plugin-persistedstate:
https://github.com/TobyMosque/quasar-v2-ssr-pinia/blob/47d05b5595b42a2c77f5e502f2ffee2b0be76a3d/src/boot/persist-store.ts
Individual stores using different storage plugins:
https://github.com/TobyMosque/quasar-v2-ssr-pinia/blob/47d05b5595b42a2c77f5e502f2ffee2b0be76a3d/src/stores/persisted-localstorage.ts
https://github.com/TobyMosque/quasar-v2-ssr-pinia/blob/47d05b5595b42a2c77f5e502f2ffee2b0be76a3d/src/stores/persisted-sessionstorage.ts
https://github.com/TobyMosque/quasar-v2-ssr-pinia/blob/47d05b5595b42a2c77f5e502f2ffee2b0be76a3d/src/stores/persisted-cookie.ts

@prazdevs
Copy link
Owner

started working on a helper for Quasar, i'll try to expose 3:

  • cookies
  • localstorage
  • sessionstorage

👍

@TobyMosque
Copy link
Contributor

TobyMosque commented Aug 1, 2022

localstorage and sessionstorage will work out of box, so u don't need to worry.
regarding the cookies, i was able to make Pinia Persisted State Plugin work with the Quasar Cookies Plugin, but my solution is nesty, to not say ugly.

first, u'll need a fake storage:

import { StorageLike } from 'pinia-plugin-persistedstate';

export const cookieStorage: StorageLike = {
  getItem(key: string) {
    return '';
  },
  setItem(key: string, value: string) {
    return;
  },
};

we just need something (the fake storage) to import in the stores, like this:

import { defineStore } from 'pinia';
import { cookieStorage } from './storages';

export const useAppStore = defineStore('app', {
  state: () => ({
    token: '',
  }),
  persist: {
    storage: cookieStorage,
  },
});

so, we can replace that fake storage by the real one in a boot or in the stores/index

import { store } from 'quasar/wrappers';
import { createPinia } from 'pinia';
import PiniaPersistedStatePlugin from 'pinia-plugin-persistedstate';
import { cookieStorage } from './storages';
import { Cookies } from 'quasar';

export default store(({ ssrContext }) => {
  const pinia = createPinia();

  // You can add Pinia plugins here
  // pinia.use(SomePiniaPlugin)

  const cookies = process.env.SERVER ? Cookies.parseSSR(ssrContext) : Cookies; // otherwise we're on client

  pinia.use(({ options }) => {
    if (!options.persist || typeof options.persist === 'boolean') {
      return;
    }
    if (options.persist.storage === cookieStorage) {
      options.persist.storage = {
        getItem(key: string) {
          return JSON.stringify(cookies.get(key));
        },
        setItem(key: string, value: string) {
          const obj = JSON.parse(value);
          cookies.set(key, obj, { path: '/', sameSite: 'Lax', secure: true });
        },
      };
    }
  });
  pinia.use(PiniaPersistedStatePlugin);
  return pinia;
});

This would be done before the PiniaPersistedStatePlugin be configured.

here the source:
https://github.com/TobyMosque/ws-auth-samples-frontend/tree/persist-after/src/stores

@prazdevs
Copy link
Owner

prazdevs commented Aug 1, 2022

yeah i think i took inspiration from some of your repos as im very unfamiliar with Quasar. Idea was to provide a helper as simple to use as this :

import { boot } from "quasar/wrappers";
import { Cookies } from "quasar";
import { 
  createQuasarCookiesPersistedState 
} from "pinia-plugin-persistedstate/quasar";

export default boot(({ store, ssrContext }) => {
  store.use(createQuasarCookiesPersistedState(Cookies, ssrContext));
});

Kinda like what we did for Nuxt

@TobyMosque
Copy link
Contributor

TobyMosque commented Aug 1, 2022

maybe as a plugin.:

# the package will be named as quasar-app-extension-pinia-persistedstate
quasar ext add pinia-persistedstate

this extension will install the PiniaPersistedStatePlugin, the Cookies Plugins and register the boots.
so, in the dev land:

import { defineStore } from 'pinia';
# pinia-plugin-persistedstate/quasar is an alias to quasar-app-extension-pinia-persistedstate
import { CookieStorage, /*LocalStorage, SessionStorage,IndexedStorage*/ } from 'pinia-plugin-persistedstate/quasar';

export const useAppStore = defineStore('app', {
  state: () => ({
    token: '',
  }),
  persist: {
    storage: CookieStorage,
  },
});

more about quasar extensions:
https://quasar.dev/app-extensions/tips-and-tricks/inject-quasar-plugin

@prazdevs
Copy link
Owner

prazdevs commented Aug 1, 2022

that's fairly interesting. i never use quasar (cause its wayyy too heavy for what i do with vue). But whenever i have time i'll look into maybe making a quasar plugin as you suggest!
(or someone can try to do it 😄)

thanks a lot!

@TobyMosque
Copy link
Contributor

cause its wayyy too heavy for what i do with vue.

Fallacies I hear every day ;D
Quasar is suitable for all project sizes, even the smallest ones.

@prazdevs
Copy link
Owner

prazdevs commented Aug 2, 2022

v2.1.0 now offers quasar helpers. Docs are also updated with how to use it!

I kept it iso with the way the Nuxt helper is made for now.

Closing this for now? feel free to reopen if you feel like something is off/odd/broken

@prazdevs prazdevs closed this as completed Aug 2, 2022
@prazdevs prazdevs added the ✨ enhancement New feature or request label Aug 2, 2022
@mohamadsdf
Copy link

localstorage and sessionstorage will work out of box, so u don't need to worry. regarding the cookies, i was able to make Pinia Persisted State Plugin work with the Quasar Cookies Plugin, but my solution is nesty, to not say ugly.

first, u'll need a fake storage:

import { StorageLike } from 'pinia-plugin-persistedstate';

export const cookieStorage: StorageLike = {
  getItem(key: string) {
    return '';
  },
  setItem(key: string, value: string) {
    return;
  },
};

we just need something (the fake storage) to import in the stores, like this:

import { defineStore } from 'pinia';
import { cookieStorage } from './storages';

export const useAppStore = defineStore('app', {
  state: () => ({
    token: '',
  }),
  persist: {
    storage: cookieStorage,
  },
});

so, we can replace that fake storage by the real one in a boot or in the stores/index

import { store } from 'quasar/wrappers';
import { createPinia } from 'pinia';
import PiniaPersistedStatePlugin from 'pinia-plugin-persistedstate';
import { cookieStorage } from './storages';
import { Cookies } from 'quasar';

export default store(({ ssrContext }) => {
  const pinia = createPinia();

  // You can add Pinia plugins here
  // pinia.use(SomePiniaPlugin)

  const cookies = process.env.SERVER ? Cookies.parseSSR(ssrContext) : Cookies; // otherwise we're on client

  pinia.use(({ options }) => {
    if (!options.persist || typeof options.persist === 'boolean') {
      return;
    }
    if (options.persist.storage === cookieStorage) {
      options.persist.storage = {
        getItem(key: string) {
          return JSON.stringify(cookies.get(key));
        },
        setItem(key: string, value: string) {
          const obj = JSON.parse(value);
          cookies.set(key, obj, { path: '/', sameSite: 'Lax', secure: true });
        },
      };
    }
  });
  pinia.use(PiniaPersistedStatePlugin);
  return pinia;
});

This would be done before the PiniaPersistedStatePlugin be configured.

here the source: https://github.com/TobyMosque/ws-auth-samples-frontend/tree/persist-after/src/stores

thanks aloooot man <3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨ enhancement New feature or request ✋ help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

5 participants