prazdevs / pinia-plugin-persistedstate

💾 Configurable persistence and rehydration of Pinia stores.
https://prazdevs.github.io/pinia-plugin-persistedstate/
MIT License
2.12k stars 121 forks source link

Feature: sync multi-tab #31

Closed AndreiSoroka closed 2 years ago

AndreiSoroka commented 2 years ago

What do you think about will add a functional sync multi-tab? like this https://www.npmjs.com/package/vuex-multi-tab-state

prazdevs commented 2 years ago

Hi, I think this is out of the scope of the package, but I can give you a solution for this. You can decalre your store state using setup function and the createGlobalState or createSharedComposable from VueUse, I think it fits perfectly what you're looking for! https://vueuse.org/shared/createglobalstate/

AndreiSoroka commented 2 years ago

@prazdevs thanks for your answer. Maybe is it possible add setup? like this code:

import { defineStore } from 'pinia'

export const useStore = defineStore('main', {
  state: () => {
    return {
      someState: 'hello pinia',
      nested: {
        data: 'nested pinia',
      },
    }
  },
  persist: {
    key: 'storekey',
    setup(context, presisData) {
     // ... here I can add listeners when tab is opened
    },
    storage: window.sessionStorage,
    paths: ['nested.data'],
    beforeRestore: (context) => {
      console.log('Before hydration...')
    },
    afterRestore: (context) => {
      console.log('After hydration...')
    },
  },
})

How does it help me? I can add listeners when the tab is opened like this

document.addEventListener('visibilitychange', () => { // when tab is active then need restore from other tab
  if (document.hidden) {
    return;
  }
  storeAuth.$state = storage.genericStore(
    storeAuth.$state,
    isAuthState,
    { isLoading: false },
  );
});

P.s. My temporary resolve:

Storage class (only for function genericStore)

class Storage {
  #name = '';

  constructor(name: string) {
    this.#name = name;
  }

  getPersistedState() {
    try {
      const data = localStorage.getItem(this.#name);
      if (!data) {
        return null;
      }
      return JSON.parse(data);
    } catch (_) {
      return null;
    }
  }

  setPersistedState(data: object) {
    try {
      localStorage.setItem(this.#name, JSON.stringify(data));
    } catch (e) {
      console.log(e);
    }
  }

  genericStore<Store>(
    store: Store,
    validator: (arg: unknown) => arg is Store,
    forceData: Partial<Store> = {},
  ): Store {
    const persistedState = this.getPersistedState();
    return {
      ...store,
      ...(persistedState && validator(persistedState) ? persistedState : {}),
      ...forceData,
    };
  }
}

in pinia store:

// ...
// connect storage
const storage = new Storage('store-auth');

const useStoreAuth = defineStore('auth', {
  state: (): AuthState => storage.genericStore<AuthState>(  // get data from localstorage or use default (with types)
{
      token: null,
      tokenExpiration: null,
      refresh: null,
      refreshExpiration: null,
      errorMessage: '',
      isLoading: false,
      tokenData: null,
    },
    isAuthState,
    {
      isLoading: false, // just reset part of state
    },
  )
/// .....
});

const storeAuth = useStoreAuth();

storeAuth.$subscribe((mutation, state) => { // save each mutation
  if (mutation.type === 'patch object') {
    storage.setPersistedState(state);
  }
});

document.addEventListener('visibilitychange', () => { // when tab is active then need restore from other tab
  if (document.hidden) {
    return;
  }
  storeAuth.$state = storage.genericStore(
    storeAuth.$state,
    isAuthState,
    { isLoading: false },
  );
});

export default useStoreAuth;
throrin19 commented 2 years ago

@AndreiSoroka you have this plugin for pinia to make the sync between tabs. I don't know if we can use it with persistentstate plugin : https://github.com/wobsoriano/pinia-shared-state

N0tExisting commented 2 years ago

You can use afterRestore as the setup hook

IlyaSemenov commented 1 year ago

pinia-shared-state works, put this to plugins/1-pinia-shared-state.ts:

import { PiniaSharedState } from "pinia-shared-state"

export default defineNuxtPlugin(() => {
  const pinia = useNuxtApp().$pinia
  pinia.use(PiniaSharedState({}))
})
AndreiSoroka commented 1 year ago

@AndreiSoroka you have this plugin for pinia to make the sync between tabs. I don't know if we can use it with persistentstate plugin : https://github.com/wobsoriano/pinia-shared-state

I use https://vueuse.org/core/useStorage/ where no need to use MF (Module Federation). With MF https://github.com/vueuse/vueuse/issues/2916#issuecomment-1489032756