npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

vuex-stores

v1.0.0

Published

Store objects for Vuex, a simple and more fluid API for state-management.

Downloads

179

Readme

Vuex Stores 🗄

Gem Version Build Status Code Climate License

Store objects for Vuex, a simple and more fluid API for state-management.

Why? 🤔

Dispatching actions and injecting getters in Vuex requires using String namespaces and action names, which is verbose and makes it hard to detect typos.

Injecting state, getters, and actions using the map helpers is sometimes cumbersome, and is only suitable for components.

Store objects address these issues by allowing access to state and getters as properties, and dispatching actions easily by using plain method calls.

Installation ⚙️

npm install --save vuex-stores

or if using yarn:

yarn add vuex-stores

API ⌨️

registerAndGetStore allows to dynamically register a module in the specified Vuex store, returning a Store Object which can be used to easily access state, getters, and actions, abstracting away the namespace for that module.

const WindowStore = registerAndGetStore(vuexStore, { namespace, state, getters, mutations, actions })

State 🗃

State can be accessed as properties in the store object:

const state = {
  isFullscreen: false,
  windowHeight: 768,
  windowWidth: 1024,
}

// A property is available for every property in the state:

WindowStore.isFullscreen // false
WindowStore.windowHeight // 768
WindowStore.windowWidth // 1024

Getters

Getters can be accessed as properties in the store object:

const getters = {
  windowSize (state) {
    return state.windowHeight * state.windowWidth
  },
}

// A property is available for every getter:

WindowStore.windowSize // 1024 * 768 = 786,432

Actions ⚡️

Actions can be dispatched by calling methods in the store object:

export const actions = {
  setFullscreen ({ commit }, isFullscreen) {
    commit('SET_FULLSCREEN', isFullscreen)
  },
  updateWindowSize ({ commit }, size = { height: window.innerHeight, width: window.innerWidth }) {
    commit('SET_WINDOW_SIZE', size)
  },
}

// A method is available for every action:
WindowStore.setFullscreen(true)
WindowStore.updateWindowSize()

vuexStore.dispatch('window/updateWindowSize', { width: 1024, height: 768 })
// becomes the more natural
WindowStore.updateWindowSize({ width: 1024, height: 768 })

By convention, mutations should be an internal detail, so they are not exposed.

mapState, mapGetters, mapActions

These usual helpers are available, allowing us to inject properties and methods in a component, without having to deal with the namespace:

computed: {
  ...WindowStore.mapState('windowHeight', 'windowWidth'),
  ...WindowStore.mapGetters('windowSize'),
},
methods: {
  ...WindowStore.mapActions('setFullscreen')
},

These are mostly helpful when the values are used in the template. Else, we have a better option that doesn't require all that boilerplate:

methods: {
  onToggleFullscreen (event) {
    WindowStore.setFullscreen(!WindowStore.isFullscreen)
  },
},

An additional benefit is that references to the state and actions are more explicit, doesn't require manual boilerplate, making the code easier to understand and refactor 😀

watch 👁

Makes it convenient to be reactive to a value in a store outside of components:

WindowStore.watch('windowSize', windowSize => console.log('onWindowSizeChange', windowSize))

WindowStore.watch('isNavigating',
  isNavigating => isNavigating ? NProgress.start() : NProgress.done(),
  { sync: true }, // Any watcher options can be provided, such as `immediate`.
)

Other less commonly used API properties and methods include:

  • buildStoreObject: Like registerAndGetStore, but doesn't call registerModule.
  • registerModule: Used internally by registerAndGetStore to register the module in Vuex.
  • unregisterModule: Can be used to remove the module from the Vuex store.
  • moduleNamespace: The module name for this store object in the Vuex store, relevant if using a factory pattern.

Recommended Setup 🛠

The recommended setup involves exporting the Vuex.Store:

// @app/store.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  strict: process.env.NODE_ENV !== 'production'
})

export default store

And creating one file per store module, exporting the store object:

// @stores/ModalStore.js

import store from '@app/store'
import { registerAndGetStore } from '@helpers/StoreHelper'

let newModalIds = 0

// The namespace for this store module.
const namespace = 'modals'

const state = () => ({
  // Modals that are currently being displayed.
  modals: [],
})

const getters = {
  // Checks if a modal with the specified id is currently open.
  isModalOpen (state) {
    return id => state.modals.some(modal => modal.id === id)
  },
}

const mutations = {
  ADD_MODAL (state, modal) {
    state.modals.push(modal)
  },
  REMOVE_MODAL (state, { id }) {
    removeBy(state.modals, modal => modal.id === id)
  },
  CLOSE_ALL_MODALS (state) {
    state.modals = []
  },
}

const actions = {
  // Adds a modal to the current window.
  addModal ({ commit, getters }, { component, attrs, listeners, id = `modal-${newModalIds++}` }) {
    if (!getters.isModalOpen(id)) {
      commit('ADD_MODAL', { component, attrs, listeners, id })
    }
    return id
  },
  // Removes a modal from the current window.
  removeModal ({ commit, getters }, { id }) {
    if (getters.isModalOpen(id)) commit('REMOVE_MODAL', { id })
  },
  // Closes all modals.
  closeAllModals ({ commit, state }) {
    if (state.modals.length > 0) commit('CLOSE_ALL_MODALS')
  },
}

export default registerAndGetStore(store, { namespace, state, getters, mutations, actions })

This makes it very convenient to import the store object from a component:

// @components/ModalManager.vue

<script>
import ModalsStore from '@stores/ModalsStore'

export default {
  name: 'ModalManager',
  computed: {
    modals () {
      return ModalsStore.modals
    },
  },
  beforeMount () {
    // Hide modals when visiting a different route.
    if (this.$router) this.$router.afterEach(ModalsStore.closeAllModals)
  },
  methods: {
    onModalClose (modal, event) {
      if (!event.defaultPrevented) ModalsStore.removeModal(modal)
    },
  },
}
</script>

<template>
  <span class="modal-manager">
    <component
      :is="modal.component"
      v-for="modal in modals"
      :id="modal.id"
      :key="modal.id"
      v-bind="modal.attrs"
      v-on="modal.listeners"
      @modal:close.native="onModalClose(modal, $event)"
    />
  </span>
</template>

The pattern described above is just one of many possibilities.

Feel free to check the tests for additional usage examples, and setup options.

Dynamic Stores (Factory) 💠

What happens if we need more than one instance of a store? Instead of exporting a single store object, we can export a function that dynamically registers a new store object on each invocation.

For example:

// @stores/ModalStoreFactory

import { uniqueId } from 'lodash'

export default (id = uniqueId(namespace)) =>
  registerAndGetStore(store, { namespace: id, state, getters, mutations, actions })

Nothing prevents you from creating a Map of store objects, and dynamically unregistering them once they are no longer necessary to free up some memory.

Let me know if you come up with new or creative ways to use it 😃

Advantages and Benefits

  • The text namespace of a store module becomes an implementation detail (transparent to the user).
  • Typos in action names fail fast (the method does not exist, instead of being ignored).
  • Dispatching actions is as simple as calling a method, which matches the store definition of an action, and feels very natural.
  • State or getters are properties in the store object and can be easily retrieved.
  • Mapping state, getters, and actions is easier than ever.
  • Dispatching an action from methods in a component doesn't require injecting the helper with mapActions.

The result feels very natural, prevents all kind of mistakes, and works nicely when used in conjunction with ES6 modules.

Because imports are strict, we get clear errors if we have a typo in the store name, so refactoring becomes a lot easier, usually as simple as search and replace.