Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@ddanger
Created July 26, 2018 01:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ddanger/21d7f4bd3580d2041b7c56ca04b25b8b to your computer and use it in GitHub Desktop.
Save ddanger/21d7f4bd3580d2041b7c56ca04b25b8b to your computer and use it in GitHub Desktop.
/**
* Create a Vuex store module to represent states for an asynchronous API getter.
*
* Includes defaultState, actions, and mutations for a standard value gotten via asynchronous call.
* See defaultState() function for list of states included.
*
* Usage:
* Assuming we have an async call to get documents (getDocuments) which takes a payload object as an arg, here's what we can do:
*
* ----- store.js -----
import Vue from 'vue'
import Vuex from 'vuex'
import buildAsyncModule from './vuex-async-module-builder'
import actions from './actions'
import getters from './getters'
import mutations from './mutations'
import { getDocuments } from '@/api'
const modules = {
documents: buildAsyncModule({ fnApiCall: getDocuments })
}
export default new Vuex.Store({
actions,
getters,
modules,
mutations,
state: rootStateData()
})
* ----- Documents.vue -----
<template>
<div>
<button @click="$store.dispatch('documents/GET')">Get Documents</button>
<div v-if="documents.pending">Fetching documents...</div>
<div v-else-if="documents.spinning">Displaying delayed spinner while still fetching documents...</div>
<div v-else-if="documents.empty">No documents found</div>
<div v-else-if="documents.error">Error getting documents</div>
<div v-else-if="documents.hasValue">
<pre>{{ documents.value }}</pre>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: mapState([
'documents'
])
}
</script>
*
* Options object:
* @param {*} fnApiCall - The API call to get the item(s)
* @param {*} fnIsEmpty - (OPTIONAL) A callback function that when passed the result of the API call, returns true if the result is empty. If your expected result is an array, the default should work fine.
* @param {*} initialValue - (OPTIONAL) The store state value assigned initially
* @param {*} spinnerDelay - (OPTIONAL) The number of milliseconds to delay before committing the "spinning" mutation
*
*/
var vuexAsyncModule = (function () {
return { build: buildAsyncModule }
function buildAsyncModule ({
fnApiCall,
fnIsEmpty = (result) => (!result || !result.length), // a default for array values
initialValue = [], // a default for array values
spinnerDelay = 1000 // number of milliseconds to wait after the api call starts before committing the "spinning" mutation
}) {
if (typeof fnApiCall !== 'function') throw new TypeError('Must pass functon: fnApiCall')
if (typeof fnIsEmpty !== 'function') throw new TypeError('Must be a function: fnIsEmpty')
if (!Number.isInteger(spinnerDelay)) throw new TypeError('Must pass number: spinnerDelay')
return {
namespaced: true,
state: defaultState(),
actions: actions(),
mutations: mutations()
}
function defaultState () {
return {
empty: false, // whether we got a value, but it was essentially empty
error: false, // whether there was an error getting the value (and so the value is meaningless)
hasValue: false, // whether we've gotten a value (non-empty, non-error) from the async call
pending: false, // whether we're currently in an async call getting this
spinning: false, // whether the spinner should be visible in the UI
value: initialValue // the value resulting from the async call
}
}
function actions () {
return {
GET ({ commit, state }, payload) {
commit('RESET')
commit('PENDING')
setTimeout(() => {
if (state.pending) {
commit('SPINNING')
}
}, spinnerDelay)
return fnApiCall(payload)
.then(result => {
fnIsEmpty(result)
? commit('SET_EMPTY')
: commit('SET', result)
return result
})
.catch(error => {
console.error(error)
commit('SET_ERROR')
})
}
}
}
function mutations () {
return {
RESET (state) {
state = replaceState(state, defaultState())
},
PENDING (state) {
state.pending = true
},
SPINNING (state) {
state.spinning = true
},
SET_EMPTY (state) {
endPending(state)
state.empty = true
state.error = false
state.hasValue = false
},
SET_ERROR (state) {
endPending(state)
state.empty = false
state.error = true
state.hasValue = false
},
SET (state, payload) {
endPending(state)
state.empty = false
state.error = false
state.hasValue = true
state.value = payload
}
}
}
}
function endPending (state) {
state.pending = false
state.spinning = false
}
function replaceState (state, newState) {
Object.keys(newState).forEach(newStateKey => {
state[newStateKey] = newState[newStateKey]
})
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment