# Stores

Nodewood uses Vuex to handle state management. Stores are stored in a feature's ui/stores folder, and configured in the feature's ui/init.js file:

async initStores(store) {
  await store.registerModule(
    'Users',
    await import(/* webpackChunkName: "users" */ '#features/users/ui/stores/UsersStore'),
  );
}

# webpackChunkName

Essentially, all imports that share the same webpackChunkName will be bundled into the same chunk file. This allows the user to load the minimum amount of code necessary to boot the app, and progressively loads additional code as they navigate through your app. This results in faster load times for your users and solves a common complaint against single-page applications (slow load speed).

# Using stores

Stores are namespaced, which means they can be used in your apps as follows:

<template>
  <div>
    <button @click="sendConfirmEmail">Click here</button>
  </div>
</template>

<script>
import { mapActions } from 'vuex';

export default {
  methods: {
    mapActions('Users', [
      'sendConfirmEmail',
    ]),
  },
};
</script>

# Waiting on network access

Should you wish to display a spinner or something similar to indicate to the user that network access is happening, a helper library called vue-loadable has been added. Vue-loadable is a simple helper that doesn't require significant changes to your code:

<template>
  <div>
    <button
      :disabled="$isLoading('sendConfirmEmail')"
      @click="sendConfirmEmail"
    >
      Click here
    </button>
  </div>
</template>

<script>
import { mapActions } from 'vuex';
import { mapLoadableMethods } from 'vue-loadable';

export default {
  methods: {
    ...mapLoadableMethods(
      mapActions('Users', [
        'sendConfirmEmail',
      ]),
    ),
  },
};
</script>

To prevent a "flicker" effect, where forms are disabled for only a fraction of a second and users are unsure if their actions have taken effect, a helper method called delayMin has been provided, to ensure a minimum amount of time has been waited on before the async function returns. Caution must be taken to ensure this value is not set too high, however.

Typically, you would use this in your store, not your components:

async confirmEmail({ commit }, { token }) {
  await delayMin(
    500,
    request.post('/api/public/confirm-email').send({ token }),
  );
},

# Adding a new store

To add a new store with some sample code to get you rolling, run:

nodewood add store FEATURE NAME

Replace FEATURE with the name of the feature you wish to add a store for, and NAME with the name of the store you wish to create. By default, stores will be registered in the feature's ui/init.js, but if you wish to register the store manually, add --no-init to the end of the command.

# Adding watchers

Sometimes, you may want to update or calculate store values based on data from another module (e.g. based on a value of the current user). The easiest way to do this is to add a watcher, but where? Sometimes there may be no good single Component to add this watcher to, as the update/calculation is more global in nature.

In this case, we can add this watcher to the optional initWatchers function, configured in the feature's ui/init.js file. These functions are only called after every store has been loaded, ensuring it is safe to refer to any other store in the watchers you configure.

An example is where we load/update the StripeConfig in the SubscriptionsStore based on the currently-loaded user's currency:

/**
 * Initialize watchers on Vuex stores for this feature.
 *
 * @param {Vuex} store - Vuex store to intialize store modules.
 */
async initWatchers(store) {
  store.watch(
    (state) => state.Users.current,
    (watched) => {
      store.commit('Subscriptions/initializeStripeConfig', { currency: watched.currency });
    },
  );
}