vuex-router-actions
vuex-router-actions copied to clipboard
Streamline complex Vuex actions and have fast and secure asynchronous routing in your app.
vuex-router-actions
The library you've been waiting for to streamline complex Vuex actions and have fast and secure asynchronous routing in your app.
Checkout this example to see how this library can be used to create a Slack clone!
Goals
- To make it easier to use Vuex actions.
- To make it easier to produce apps which can load data asynchronously before being routed to a page and also check along the way whether that user can go to that page - and also what to do when they cannot.
- To cache action results so traversing through an app is as efficient as possible.
- To make it easier to track asynchronous actions and keep the user notified when the app is loading.
Features
- Cache the results of an action based on logic or a cache key.
- Listen to when actions are started, resolved, rejected, ended, and all pending actions are done.
- Call a mutation with a flag on whether a set of actions are currently running.
- Check the state of the store to determine whether an action can continue (subsequently a route).
- Keep a routed Vue component from loading or updating until an action is finished.
- If an action has exited because a user cannot visit a route, provide a way to redirect the user somewhere else.
- Communicate to the user when one or more actions are being processed (makes it easy to create loading screens).
Contents
- Goals
- Features
- Dependencies
- Installation
-
API
- actionsWatch
- actionsLoading
- actionCached
- actionsCached
- actionCachedConditional
- actionsCachedConditional
- actionCachedResults
- actionsCachedResults
- actionsDestroyCache
- actionsProtect
- actionBeforeRoute
- actionOptional
Dependencies
No build time dependencies, but this library is used in conjunction with vuex
and optionally vue-router
.
Installation
npm
Installation via npm : npm install --save vuex-router-actions
API
actionsWatch
Watches the given actions and invokes the callbacks passed in the options of VuexRouterActions
. If the result of an action is a promise - it is watched and immediately invokes onActionStart
followed by onActionResolve
or onActionReject
and then onActionEnd
. If the result of an action is not a promise then onActionStart
and onActionEnd
are invoked immediately. If there are no more watched actions being executed then onActionsDone
is invoked after the last onActionEnd
.
import VuexRouterActions, { actionsWatch } from 'vuex-router-actions'
const plugin = VuexRouterActions({
onActionStart (action, num, context, payload) {},
onActionEnd (action, num, context, payload, result, resolved) {}
})
const store = new Vuex.Store({
plugins: [plugin],
actions: {
...actionsWatch({
loadThis (context, payload) {},
loadThat (context, payload) {} // return a Promise
})
}
})
You can also pass in options as the 2nd argument of of actionsWatch
to override any options passed to the plugin:
...actionsWatch({
loadThis (context, payload) {},
loadThat (context, payload) {} // return a Promise
}, {
onActionStart (action, num, context, payload) {},
onActionEnd (action, num, context, payload, result, resolved) {}
})
actionsLoading
Calls a mutation on a store when any of the passed actions are running and when they stop. This provides an easy way to signal to the user when something is loading, even a complex set of asynchronous actions.
import VuexRouterActions, { actionsLoading } from 'vuex-router-actions'
const store = new Vuex.Store({
plugins: [VuexRouterActions()],
state: {
loading: false
},
mutations: {
setLoading (state, loading) {
state.loading = loading
}
},
actions: {
...actionsLoading('setLoading', {
page1 (context, payload) {},
page2 (context, payload) {}
})
}
})
Instead of a mutation you can optionally pass a function which is given a context
and the loading
flag.
...actionsLoading(
(context, loading) => context.commit('setLoading', loading),
{
page1 (context, payload) {},
page2 (context, payload) {}
}
)
actionCached
Produce an action with a cached result based on some cache key. The action will always run the first time. The cached results of the action are "cleared" when a different key is returned for a given action.
import VuexRouterActions, { actionCached } from 'vuex-router-actions'
const store = new Vuex.Store({
plugins: [VuexRouterActions()],
actions: {
loadPage: actionCache({
getKey: (context, payload) => payload, // look at store, getters, payload, etc
action: (context, payload) => null // some result that can be cached
})
}
})
By default the results of getKey
are passed to JSON.stringify
to make it an easily comparable value. If you want to override this functionality you can pass createCacheKey (key: any): string
to the plugin options or as the second argument to actionsCached:
loadPage: actionCached({
getKey: (context, payload) => payload, // look at store, getters, payload, etc
action: (context, payload) => null // some result that can be cached
}, {
createCacheKey(key) { // the keys for these actions are arrays, we will join them to produce a string.
return key.join('-')
}
})
actionsCached
Similar to actionCached except it handles multiple actions. This function also takes optional cache options as the second argument.
import VuexRouterActions, { actionsCached } from 'vuex-router-actions'
const store = new Vuex.Store({
plugins: [VuexRouterActions()],
actions: {
...actionsCached({
loadPage: {
getKey: (context, payload) => payload, // look at store, getters, payload, etc
action: (context, payload) => null // some result that can be cached
}
})
}
})
actionCachedConditional
Produce an action with a cached result based on some condition. The action will always run the first time. When isInvalid
returns true the action is
re-evaluated.
import VuexRouterActions, { actionCachedConditional } from 'vuex-router-actions'
const store = new Vuex.Store({
plugins: [VuexRouterActions()],
actions: {
loadPage: actionCachedConditional({
isInvalid: (context, payload) => true, // look at store, getters, payload, etc
action: (context, payload) => null // some result that can be cached
})
}
})
actionsCachedConditional
Similar to actionCachedConditional except it handles multiple actions.
import VuexRouterActions, { actionsCachedConditional } from 'vuex-router-actions'
const store = new Vuex.Store({
plugins: [VuexRouterActions()],
actions: {
...actionsCachedConditional({
loadPage: {
isInvalid: (context, payload) => true, // look at store, getters, payload, etc
action: (context, payload) => null // some result that can be cached
}
})
}
})
actionCachedResults
Produce an action with multiple cached results. The action has a getKey
function which is optional - when the result of that function changes it clears the cache for all the results for the action. The action has a required getResultKey
function which is unique to that action call - and if that key has not ran for that action it is ran, otherwise the cached result is returned.
import VuexRouterActions, { actionCachedResults } from 'vuex-router-actions'
const store = new Vuex.Store<TestStore>({
getGroupUser: actionCachedResults({
getKey: ({state}) => state.group.id, // When the group id changes, the cached results clear
getResultKey: (context, user_id) => user_id,
action: ({dispatch, state}, user_id) => null // TODO Return User instance relative to the given group
})
})
actionsCachedResults
Similar to actionCachedResults except it handles multiple actions.
import VuexRouterActions, { actionsCachedResults } from 'vuex-router-actions'
const store = new Vuex.Store<TestStore>({
...actionsCachedResults({
getGroupUser: {
getKey: ({state}) => state.group.id, // When the group id changes, the cached results clear
getResultKey: (context, user_id) => user_id,
action: ({dispatch, state}, user_id) => null // TODO Return User instance relative to the given group
}
})
})
actionsDestroyCache
This function destroys all caches created with actionsCached
, actionsCachedConditional
, and actionsCachedResults
. This is a necessary function when you are implementing something like a Sign Out function in your app.
import { actionsDestroyCache } from 'vuex-router-actions'
// signOut
actionsDestroyCache()
actionsProtect
Creates "protection" actions. These are functions which return a truthy or falsy value and the action returned produces a Promise that is resolved or rejected. This is useful when creating actions which load data for a route. You can add protection actions before and/or after the loading actions so it can validate the route path and afterwards validate whether the user should be able to see the given route. If the protect action returns another promise that promise is passed through, and whether it resolves or rejects determines if the action stops or continues.
import VuexRouterActions, { actionsProtect, actionBeforeRoute } from 'vuex-router-actions'
const store = new Vuex.Store({
plugins: [VuexRouterActions()],
actions: {
loadUser (context, user_id) {}, // returns promise which loads user data, also commits user to state
loadTask (context, task_id) {}, // returns promise which loads task data
loadTaskPage ({dispatch}, to) { // for actionBeforeRoute the to route is passed
return dispatch('loadUser', to.params.user)
.then(user => dispatch('hasUser'))
.then(hasUser => dispatch('loadTask', to.params.task))
.then(task => dispatch('canViewTask', task))
},
...actionsProtect({
hasUser: ({state}) => state.user && !state.user.disabled,
canViewTask: ({state}, task) => state.user.canView( task )
})
}
})
// Task page
const component = {
...actionBeforeRoute('loadTaskPage')
}
actionBeforeRoute
Dispatches an action in the store and waits for the action to finish before the routed component this is placed in is entered or updated (see beforeRouteEnter
and beforeRouteUpdate
in vue-router
). If the action resolves the routed component will be entered, otherwise getOtherwise
will be invoked to determine where the router should be redirected. getOtherwise
by default returns false which simply stops routing. If it returned undefined it would proceed as normal. If it returned a string or a route object it would redirect to that route.
// MyPage.vue
import { actionBeforeRoute } from 'vuex-router-actions'
export default {
...actionBeforeRoute('loadMyPage', (to, from, rejectReason, store, action) => {
return '/page/blocked/where/do/I/go/next'
})
}
actionOptional
Allows you to pass the results of a dispatch through this function and whether or not it resolves or rejects it won't stop from proceeding. This happens by returning a promise which always resolves. If the given promise is rejected then resolveOnReject
is passed to the resolve function of the returned Promise.
import VuexRouterActions, { actionsProtect, actionOptional } from 'vuex-router-actions'
const store = new Vuex.Store({
plugins: [VuexRouterActions()],
actions: {
loadUser (context, user_id) {}, // returns promise which loads user data, also commits user to state
loadTask (context, task_id) {}, // returns promise which loads task data
loadUserTask (context, task_id) {}, // returns a promise which may or may not return the relationship between the user and the task
loadTaskPage ({dispatch}, to) { // for actionBeforeRoute the to route is passed
return dispatch('loadUser', to.params.user)
.then(user => dispatch('hasUser'))
.then(hasUser => dispatch('loadTask', to.params.task))
.then(task => dispatch('canViewTask', task))
.then(userTask => actionOptional(dispatch('loadUserTask', to.params.task))) // <== HERE
},
...actionsProtect({
hasUser: ({state}) => state.user && !state.user.disabled,
canViewTask: ({state}, task) => state.user.canView( task )
})
}
})