DEV Community

loading...

Persist Vuex State between Page Reloads with LocalStorage and IndexedDB

Xiaoyang Liu
・3 min read

image

Wortharead, a blogs aggregator app that assembled the largest DB of 20,000+ Substack newsletters, uses Vuex to store and manage the shared state of all Vue components, such as the articles in the Today Feed, feeds followed by the user, and articles recommended in the Explore section. However, when using Vuex out-of-the-box, the Vuex state is lost and reset to default when the page is refreshed, causing unnecessary network requests.

This article will talk about how we initially solved this problem using LocalStorage and later how and why we migrated to IndexedDB (hint: LocalStorage only lets you store 5MB of data).

LocalStorage

image

To persist and rehydrate the Vuex state between page reloads, we initially chose to save the state to LocalStorage after each mutation and read the data from it when the page is reloaded. The vuex-persist plugin implements this functionality and provides extensive TypeScript type declaration.

npm install --save vuex-persist
Enter fullscreen mode Exit fullscreen mode

To use it, install the plugin and import VuexPersistence from vuex-persist. Set storage to window.localStorage and register vuexLocal.plugin as a Vuex plugin. Refresh the page and then the state will be saved to LocalStorage.

const vuexLocal = new VuexPersistence({
  storage: window.localStorage,
});
Vue.use(Vuex);
const store = new Vuex.Store<State>({
  state: { ... },
  mutations: { ... },
  actions: { ... },
  plugins: [vuexLocal.plugin],
});
export default store;
Enter fullscreen mode Exit fullscreen mode

IndexedDB

image

After several iterations, Wortharead decided to save article content storage in Vuex to ensure that users can read cached articles offline. Since LocalStorage is limited to about 5MB, the large amount of article data quickly exhausted the storage quota, causing unpredictable errors. Therefore, we chose to migrate the persisted state to IndexedDB.

npm install --save localforage
Enter fullscreen mode Exit fullscreen mode

localForage implements a simple, localStorage-like API for IndexedDB, which is compatible with vuex-persist. Import localForage from the package and set storage to localForage. Since localForage storage is asynchronous, set the asyncStorage option to true.

import Vue from 'vue';
import Vuex from 'vuex';
import VuexPersistence from 'vuex-persist';
import localForage from 'localforage';
const vuexLocal = new VuexPersistence({
  storage: localForage,
  asyncStorage: true,
});
Vue.use(Vuex);
const store = new Vuex.Store<State>({
  state: { ... },
  mutations: { ... },
  actions: { ... },
  plugins: [vuexLocal.plugin],
});
export default store;
Enter fullscreen mode Exit fullscreen mode

When we first attempted to use this library, it seemed to work: data was being successfully stored and the app worked. However, on a page refresh, the data disappeared. We worried that the migration to IndexedDB may not be so easy. After some exploration though, we figured out the issue.

Since localForage is promise-based storage, the state will not be immediately restored into Vuex. It will go into the event loop and will finish when the JS thread is empty, which could invoke a delay of few seconds. vuex-persist injected a restored property to the store object, which contains a Promise that will be resolved after the state is restored. The beforeEach() hook in vue-router could cause the app to wait for vuex-persist to restore the state before taking any further actions.

import Vue from 'vue';
import Router from 'vue-router';
import { store } from '@/store'; // the location of Vuex store

Vue.use(Router);
const router = new Router({
  // define the routes
});

router.beforeEach(async (to, from, next) => {
  await store.restored;
  next();
});

export default router;
Enter fullscreen mode Exit fullscreen mode

Discussion (0)