bencodezen / vue-enterprise-boilerplate

An ever-evolving, very opinionated architecture and dev environment for new Vue SPA projects using Vue CLI.
7.77k stars 1.32k forks source link

Reset every store module after user logs out #111

Closed rickyruiz closed 5 years ago

rickyruiz commented 5 years ago

When a user logs out, many modules need to be 'reset' or changed back to some defaults because they are dependent on the current user actions/roles/permissions.

As I added more modules to the store, I found necessary to use a reset action for several modules triggered by the logout action. This is similar to what you currently have for the init action that runs for every module.

You currently do:

// Automatically run the `init` action for every module,
// if one exists.
for (const moduleName of Object.keys(modules)) {
  if (modules[moduleName].actions && modules[moduleName].actions.init) {
    store.dispatch(`${moduleName}/init`)
  }
}

What I did, that may be useful in this boilerplate, is the following:

  1. Wrap the modules loop in a private helper in the store index:
//===
// Private helpers
//===

function runActionForEveryModule(actionName) {
  if (modules) {
    for (const moduleName of Object.keys(modules)) {
      const { actions } = modules[moduleName]
      if (actions && actions[actionName]) {
        store.dispatch(`${moduleName}/${actionName}`)
      }
    }
  }
}
  1. Create a root action called 'resetModules' in the Store instance:
import Vue from 'vue'
import Vuex from 'vuex'
import modules from './modules'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules,
  actions: {
    resetModules() {
      // Automatically run the `reset` action for every module,
      // if one exists.
      runActionForEveryModule('reset')
    },
  },
  // Enable strict mode in development to get a warning
  // when mutating state outside of a mutation.
  // https://vuex.vuejs.org/guide/strict.html
  strict: process.env.NODE_ENV !== 'production',
})

// Automatically run the `init` action for every module,
// if one exists.
runActionForEveryModule('init')

export default store
  1. In the auth module add a 'reset' action that changes the state back to its defaults:
  // This is automatically run in `src/state/store.js` when the user
  // logs off, along with any other actions named `reset` in other modules.
  reset({ commit }) {
    commit('SET_CURRENT_USER', null)
  },
  1. Change the logout action to call the root resetModules action:
  // Logs out the current user.
  logOut({ dispatch}) {
    dispatch('resetModules', null, { root: true })
  },

Now, we can add a reset action in any other module that needs it which is triggered by the user logging off.

Ie.

A dashboard that changes depending on the current user role.

const actions: ActionTree<DashBoardState, RootState> = {
  reset({ commit, dispatch }) {
    commit(REMOVE_LOCAL_STORE_FILTER)
    commit(RECEIVE_FILTER, getDashboardFilter())
    dispatch('localStoreFilter')
  },
  ...
chrisvfritz commented 5 years ago

If there's information that's specific to the user, like roles/permissions, that you have to update every time the user changes, what would you think about keeping it on the user object? That's what I've done in the past and it seems like a simpler solution to me.

rickyruiz commented 5 years ago

I believe I took the issue to another direction talking about user roles and permissions. I do keep my user roles and permissions in a user object.

I'll try to explain the case/problem:

Lets say we have a module called dashboard where we have

const state: DashBoardState = {
  filter: getDashboardFilter(),
}

Which could be moved to an init action, that would be called when we create the store instance.

After a user logs off, that filter needs to go back to its defaults, because it may have changed.

How would you call the init action again / go back to the initial state? The store is not destroyed.

How would you reset a module state property, in this case the filter; dispatching a dashboard action from the auth logout action?


Here are the helpers:

// ===
// Private helpers
// ===
function getSavedDashboardFilter(): DashBoardFilter | null {
  return appStorage.getObjectItem(DASHBOARD_LOCAL_STORAGE_KEY)
}

function saveDashboardFilter(filter: DashBoardFilter) {
  appStorage.setObjectItem(DASHBOARD_LOCAL_STORAGE_KEY, filter)
}

function removeDashboardFilter() {
  appStorage.removeItem(DASHBOARD_LOCAL_STORAGE_KEY)
}

function getDashboardFilter(): DashBoardFilter {
  const savedFilter = getSavedDashboardFilter()

  if (savedFilter) return savedFilter

  const filter: DashBoardFilter = {
    startingDate: null,
    endingDate: null,
    // ... removed properties for simplicity
  }

  saveDashboardFilter(filter)

  return filter
}
chrisvfritz commented 5 years ago

If I'm understanding correctly, it sounds like you may want something like this:

init() {
  dispatch('getDashboardFilter')
  const store = require('@state/store').default
  store.watch(
    state => state.auth.currentUser, 
    () => dispatch('getDashboardFilter')
  )
}
rickyruiz commented 5 years ago

I guess I could go with a watcher, it seems it is a cleaner solution. Thanks for your time Chris, I really appreciate it.

chrisvfritz commented 5 years ago

Happy to help. 🙂