championswimmer / vuex-persist

A Vuex plugin to persist the store. (Fully Typescript enabled)
http://championswimmer.in/vuex-persist
MIT License
1.67k stars 117 forks source link

Store works when using "window.localStorage" but not when using "localForage".. any pointers would be appreciated #232

Open wattsie opened 3 years ago

wattsie commented 3 years ago

Hi All, New to "vuex-persist" mainly been using another persist package, and started having issues with exceeding storage size. Data is around 6mb, but looks like LocalStore is limited to less than ~5Mb.

So thought I would take a look into using "vuex-persist" and IndexedDB (via localForage).

As such, I have configured my store as follows:

import {createLogger, createStore} from 'vuex'
import VuexPersist from 'vuex-persist'
import localForage from 'localforage'

// Store Modules
import settings from '@/store/modules/settings'
import data from '@/store/modules/data'

const debug = process.env.NODE_ENV !== 'production'

// Create the Persistence Store
const vuexStorage = new VuexPersist({
    key: 'data',
    storage: window.localStorage,
})

// Export the store
export default createStore({
    modules: { settings, data },
    strict: debug,
    plugins: debug ? [createLogger(), vuexStorage.plugin] : [vuexStorage.plugin],
})

When run the above, all works as expected, I can see the localStore "data" key created with content, and when refreshing the page, everything loads as expected.

However, if I change to "localForage", nothing shows up in the "IndexedDB" Browser and when page is refreshed all data is lost.

const vuexStorage = new VuexPersist({
    key: 'data',
    storage: localForage,
    modules: [ 'settings' , 'data' ],
    asyncStorage: true,
})

PS: I do find I am getting the following on both Firefox and Chrome:

Uncaught (in promise) DOMException: Failed to execute 'put' on 'IDBObjectStore': [object Array] could not be cloned.

Which is weird, as all the data is a bunch of large arrays, and localStore saves perfectly.

Any suggestions or pointers would be really appreciated.

Many thanks in advance.

Package Versions:

"vue": "^3.0.11",
"vuex": "^4.0.1",
"localforage": "^1.9.0",
"vuex-persist": "^3.1.3"
wattsie commented 3 years ago

Hi All, As it has been very quiet on this git, I have spent some more time myself.

So I have been able to work out how to get around the IDBObjectStore issue, using the reducer: (state) => JSON.parse(JSON.stringify(state))

However, if I want to specify modules, the docs say you shouldnt use a reducer.

Any idea if it is possible to apply a reducer per module?

Or a fix so that state is stripped of all non-cloneable entries before being passed to localforage?

Here is what I have working atm, however I would like to be able to have mod1, mod2 and mod3 in separate stores.

Any suggestions?

import VuexPersist from 'vuex-persist'
import localForage from 'localforage'

const store1 = localForage.createInstance({})
store1.config({
    driver      : localForage.INDEXEDDB,
    name        : 'myDb',
    version     : 1.0,
    storeName   : 'myStore',
})

const vuexStorage1 = new VuexPersist({
    key: 'vuex',
    storage: store1,
    strictMode: false,
    asyncStorage: true,
    reducer: (state) => JSON.parse(JSON.stringify(state)),
})

export default createStore({
    modules: {
        mod1,
        mod2,
        mod3,
    },
    strict: false,
    plugins: [vuexStorage1.plugin],
})

For example, if I try this, I get a complete copy of all store modules in both databases. Obviously, the converting of the global state is over ruling any module limits etc.

const store2 = localForage.createInstance({})
store2.config({
    driver      : localForage.INDEXEDDB,
    name        : 'myDb2',
    version     : 1.0,
    storeName   : 'myStore2',
})
const vuexStorage2 = new VuexPersist({
    key: 'vuex2',
    modules: ['mod2'],
    storage: myStore2,
    strictMode: false,
    asyncStorage: true,
    reducer: (state) => JSON.parse(JSON.stringify(state)),
})

and modify

plugins: [vuexStorage1.plugin, vuexStorage2.plugin],

Many thanks in advance.

chrisspiegl commented 2 years ago

I am having the same issue. No idea what may be going on there… also not really much to add, the description is already pretty on point.

toniengelhardt commented 1 year ago

I think we can do something like this:

storage: {
  getItem: async (key: string) => {
    const raw: string | null = await localForage.getItem(key)
    const data = raw ? JSON.parse(raw) : {}
    return data
  },
  setItem: async (key: string, value: any) => {
    const valueString = JSON.stringify(value)
    localForage.setItem(key, valueString)
  },
  removeItem: async (key: string) => localForage.removeItem(key),
},
asyncStorage: true,

aka. wrap the localForage getter and setter in sort of a serializer/deserializer.

This way we can still use the modules as well.