Created
July 26, 2018 01:36
-
-
Save ddanger/21d7f4bd3580d2041b7c56ca04b25b8b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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