Skip to content
Drew Althage edited this page Jul 22, 2020 · 1 revision

State Management

src/app/app-state.js

We use Vuex for managing the applications state. Each Vuex submodule is broken down mostly on a per-view basis into tier two states, and each of them with their own modules.

// src/app/app-state.js

export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {},
  modules: {
    appGlobalState,
    gageDetailState,
    newsPageState,
    riverDetailState,
    riverIndexState,
    riverSearchState,
    userState
  }
})

For example, the following are submodules of riverDetailState:

// src/app/views/river-detail/river-detail-state.js

export default {
  modules: {
    accidentsData,
    alertsData,
    bookmarksData,
    commentsData,
    creditsData,
    gageMetricsData,
    gageReadingsData,
    gageSourceData,
    galleryData,
    mapData,
    newsTabData,
    rapidsData,
    reachGagesData,
    riverDetailData
  }
}

Model

Each Vuex submodule shares the same core state model of data, error, and loading. A mutation should only modify state. Directly changing the state is considered bad practice.

const initialState = {
  data: null,
  error: null,
  loading: false
}

Then in our component templates, we can easily provide the user with operation feedback. Here's what a piece of the template could look like for loading an array of images.

<template v-if="loading">
  loading images
</template>
<template v-else-if="data">
  <div v-if="data.length">
    <img v-for="(img, index) in data" :key="index" :src="img.src" />
  </div>
  <div v-else>
    no images
  </div>
</template>
<template v-else>
  error
  <template></template
></template>

Mutations

// @/app/_templates/module-template/shared/state/module-data.js

const namespacedPrefix = '[MODULE]'

const mutationTypes = reflectKeys(
  ['DATA_SUCCESS', 'DATA_REQUEST', 'DATA_ERROR', 'DATA_RESET'],
  namespacedPrefix
)

const { DATA_ERROR, DATA_REQUEST, DATA_RESET, DATA_SUCCESS } = mutationTypes

const mutations = {
  [DATA_REQUEST] (state) {
    Object.assign(state, { loading: true, error: null })
  },

  [DATA_SUCCESS] (state, payload) {
    Object.assign(state, { loading: false, data: payload })
  },

  [DATA_ERROR] (state, payload) {
    Object.assign(state, {
      loading: false,
      data: null,
      error: payload || true
    })
  },

  [DATA_RESET] (state) {
    Object.assign(state, ...initialState)
  }
}

Actions

export const moduleActions = reflectKeys(
  ['FETCH_MODULE_DATA'],
  namespacedPrefix
)

/**
 *
 * All API calls are made with Vuex actions
 *
 */

const actions = {
  async [moduleActions.FETCH_MODULE_DATA] (context, data) {
    context.commit(DATA_REQUEST)

    const result = await fetchModuleData(data).catch(e => {
      context.commit(DATA_ERROR, e)
    })

    if (result) {
      context.commit(DATA_SUCCESS, result.data)
    }

    return result
  }
}

Then in the component, we can access the state in the computed property and dispatch actions.

computed: {
  ...mapState({
    loading: state => state.moduleState.moduledata.loading,
    data: state => state.moduleState.moduledata.data,
    error: state => state.moduleState.moduledata.error
  })
},
created () {
  this.$store.dispatch(moduleActions.FETCH_MODULE_DATA)
}

Considerations

This is not the most DRY strategy. @drewalth has put together a proof-of-concept for a more straight forward Vuex store. Consider refactoring when appropriate.

Clone this wiki locally