Lesson 05-Nuxt.js State Management

Using Vuex for State Management

Initializing Vuex Store

To use Vuex in Nuxt.js, you first need to initialize a Vuex Store. In Nuxt.js, you can define the Store by creating files in the store directory.

Creating the Store:

mkdir store
touch store/index.js
// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    count: 0,
  },
  mutations: {
    increment(state) {
      state.count++;
    },
  },
  actions: {
    increment({ commit }) {
      commit('increment');
    },
  },
  getters: {
    count: state => state.count,
  },
});

Using Vuex Store

Once the Store is defined, it is automatically injected into the context of every page component in your Nuxt.js application.

// pages/index.vue
export default {
  methods: {
    incrementCount() {
      this.$store.dispatch('increment');
    },
  },
  computed: {
    count() {
      return this.$store.getters.count;
    },
  },
};

Using Vuex Modular Structure

As your application grows in complexity, you may need to split the Store into multiple modules to make state management clearer and easier to maintain.

// store/modules/posts.js
const state = {
  list: [],
};

const mutations = {
  setPosts(state, posts) {
    state.list = posts;
  },
};

const actions = {
  async fetchPosts({ commit }, $axios) {
    const response = await $axios.$get('https://api.example.com/posts');
    commit('setPosts', response);
  },
};

export default {
  namespaced: true,
  state,
  mutations,
  actions,
};

Import the module in store/index.js:

// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import posts from './modules/posts';

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    posts,
  },
});

Using Vuex Namespaces

When the Store becomes large, using namespaces can help avoid naming conflicts.

// pages/index.vue
export default {
  async asyncData({ store, $axios }) {
    await store.dispatch('posts/fetchPosts', $axios);
  },
  computed: {
    posts() {
      return this.$store.getters['posts/list'];
    },
  },
};

Using Vuex Plugins

Vuex supports a plugin mechanism to extend its functionality. For example, you can use the vuex-persistedstate plugin to persist the Store’s state.

Install the Plugin:

npm install vuex-persistedstate --save

Use the plugin in store/index.js:

// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import createPersistedState from 'vuex-persistedstate';
import posts from './modules/posts';

Vue.use(Vuex);

export default new Vuex.Store({
  plugins: [createPersistedState()],
  modules: {
    posts,
  },
});

Using Vuex Strict Mode

To prevent unintended state changes in the development environment, you can enable Vuex’s strict mode.

// store/index.js
export default new Vuex.Store({
  strict: process.env.NODE_ENV !== 'production',
  // ...
});

Using Vuex Hot Reloading

During development, you may want to see changes to the Store reflected immediately. Vuex supports hot reloading to achieve this.

// nuxt.config.js
export default {
  buildModules: ['@nuxtjs/vuex'],
  vuex: {
    strict: true,
    devtools: true,
  },
};

Using Vuex Debugging Tools

To facilitate debugging the Vuex Store, you can use the official Vue Devtools extension.

Install Vue Devtools:

Chrome: Chrome Web Store
Firefox: Firefox Add-ons

Using Other Vuex Features

Vuex offers many additional useful features, such as nested modules, asynchronous actions, and dynamic module registration.

// store/modules/users.js
const state = {
  list: [],
};

const mutations = {
  setUsers(state, users) {
    state.list = users;
  },
};

const actions = {
  async fetchUsers({ commit }, $axios) {
    const response = await $axios.$get('https://api.example.com/users');
    commit('setUsers', response);
  },
};

export default {
  namespaced: true,
  state,
  mutations,
  actions,
};

Dynamically register the module in store/index.js:

// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import posts from './modules/posts';
import users from './modules/users';

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    posts,
  },
});

// Dynamically register the module
const store = createStore();
store.registerModule('users', users);

Vuex Dynamic Module Registration

Dynamic module registration allows you to add new modules to the Vuex Store at runtime, which is useful for scenarios where modules need to be loaded dynamically based on user permissions or other conditions.

// store/modules/users.js
const state = {
  list: [],
};

const mutations = {
  setUsers(state, users) {
    state.list = users;
  },
};

const actions = {
  async fetchUsers({ commit }, $axios) {
    const response = await $axios.$get('https://api.example.com/users');
    commit('setUsers', response);
  },
};

export default {
  namespaced: true,
  state,
  mutations,
  actions,
};

Dynamically register the module in store/index.js:

// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import posts from './modules/posts';

Vue.use(Vuex);

export default function createStore() {
  const store = new Vuex.Store({
    modules: {
      posts,
    },
  });

  // Dynamically register the module
  store.registerModule('users', users);

  return store;
}

Vuex Nested Modules

Sometimes, you may need to further subdivide modules into submodules. This can be achieved by nesting modules.

// store/modules/users.js
const state = {
  list: [],
};

const mutations = {
  setUsers(state, users) {
    state.list = users;
  },
};

const actions = {
  async fetchUsers({ commit }, $axios) {
    const response = await $axios.$get('https://api.example.com/users');
    commit('setUsers', response);
  },
};

export default {
  namespaced: true,
  state,
  mutations,
  actions,
};

Nest the module in store/index.js:

// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import users from './modules/users';

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    users: {
      namespaced: true,
      modules: {
        users,
      },
    },
  },
});

Vuex Asynchronous Actions

In Vuex, actions can be asynchronous, allowing you to call APIs or perform other asynchronous operations within actions.

// store/modules/posts.js
const state = {
  list: [],
};

const mutations = {
  setPosts(state, posts) {
    state.list = posts;
  },
};

const actions = {
  async fetchPosts({ commit }, $axios) {
    try {
      const response = await $axios.$get('https://api.example.com/posts');
      commit('setPosts', response);
    } catch (error) {
      console.error('Failed to fetch posts:', error);
    }
  },
};

export default {
  namespaced: true,
  state,
  mutations,
  actions,
};

Vuex Error Handling

Error handling is critical when dealing with asynchronous operations. You can use try-catch blocks to capture and handle errors.

// store/modules/posts.js
const actions = {
  async fetchPosts({ commit }, $axios) {
    try {
      const response = await $axios.$get('https://api.example.com/posts');
      commit('setPosts', response);
    } catch (error) {
      console.error('Failed to fetch posts:', error);
      // Trigger a global error notification
      this.$notify.error({
        title: 'Error',
        message: 'Failed to fetch posts.',
      });
    }
  },
};

Vuex Performance Optimization

For large applications, performance optimization is crucial. Here are some methods to improve Vuex performance:

  • Reduce Unnecessary Computations: Use computed properties instead of getters if the getter’s computation is costly.
  • Avoid Excessive Rendering: Use watch or computed instead of methods to reduce unnecessary re-renders.
  • Use Debouncing and Throttling: Apply debouncing or throttling techniques to frequent events to reduce unnecessary action calls.
// pages/index.vue
export default {
  data() {
    return {
      searchQuery: '',
    };
  },
  watch: {
    searchQuery: _.debounce(function (newVal) {
      this.$store.dispatch('search', newVal);
    }, 300),
  },
};

Vuex Debugging Techniques

When debugging a Vuex Store, you can use the following techniques:

  • Use Vue Devtools: Install and use Vue Devtools to inspect and debug the Vuex Store.
  • Log Recording: Add logging in actions and mutations to track state changes.
  • Unit Testing: Write unit tests to verify the Vuex Store’s behavior.

Using Pinia for State Management

Installing Pinia

First, you need to install Pinia. You can do this using npm or Yarn.

Install Pinia:

npm install pinia --save
# or with Yarn
yarn add pinia

Initializing Pinia Store

Next, you need to create a Pinia Store. Pinia uses the defineStore function to define Stores.

Creating a Store:

// store/index.js
import { defineStore } from 'pinia';

export const useMainStore = defineStore({
  id: 'main',
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++;
    },
  },
  getters: {
    doubleCount: state => state.count * 2,
  },
});

Using Pinia Store

Once the Store is defined, it can be used in your Nuxt.js application. Pinia Stores are automatically injected into the context of each page component.

// pages/index.vue
import { useMainStore } from '~/store';

export default {
  setup() {
    const mainStore = useMainStore();

    return {
      count: mainStore.count,
      doubleCount: mainStore.doubleCount,
      increment: mainStore.increment,
    };
  },
};

Using Pinia Modular Structure

As your application grows in complexity, you may need to split the Store into multiple modules to make state management clearer and easier to maintain.

// store/modules/posts.js
import { defineStore } from 'pinia';

export const usePostsStore = defineStore({
  id: 'posts',
  state: () => ({
    list: [],
  }),
  actions: {
    async fetchPosts($axios) {
      const response = await $axios.$get('https://api.example.com/posts');
      this.list = response;
    },
  },
});

Import the module in store/index.js:

// store/index.js
import { defineStore } from 'pinia';
import { usePostsStore } from './modules/posts';

export const useMainStore = defineStore({
  id: 'main',
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++;
    },
  },
  getters: {
    doubleCount: state => state.count * 2,
  },
});

export { usePostsStore };

Using Pinia Namespaces

While Pinia does not require namespaces by default, you can achieve a similar effect by creating multiple Stores.

// pages/index.vue
import { usePostsStore } from '~/store';

export default {
  setup() {
    const postsStore = usePostsStore();

    return {
      posts: postsStore.list,
      fetchPosts: postsStore.fetchPosts,
    };
  },
};

Using Pinia Hot Reloading

During development, Pinia supports hot reloading out of the box, so no additional configuration is needed to see changes to the Store immediately.

Using Pinia Type Safety

Pinia supports TypeScript, allowing your Stores to be type-safe.

// store/modules/posts.ts
import { defineStore } from 'pinia';

interface Post {
  id: number;
  title: string;
}

export const usePostsStore = defineStore({
  id: 'posts',
  state: () => ({
    list: [] as Post[],
  }),
  actions: {
    async fetchPosts($axios) {
      const response = await $axios.$get<Post[]>('https://api.example.com/posts');
      this.list = response;
    },
  },
});

Using Pinia Persistence

To persist the Store’s state, you can use third-party libraries like pinia-plugin-persist.

Install the Plugin:

npm install pinia-plugin-persist --save

Use the plugin in the Store:

// store/index.js
import { defineStore } from 'pinia';
import { usePersist } from 'pinia-plugin-persist';

export const useMainStore = defineStore({
  id: 'main',
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++;
    },
  },
  getters: {
    doubleCount: state => state.count * 2,
  },
  plugins: [usePersist()],
});

Using Pinia Asynchronous Actions

In Pinia, actions can be asynchronous, allowing you to call APIs or perform other asynchronous operations.

// store/modules/posts.js
import { defineStore } from 'pinia';

export const usePostsStore = defineStore({
  id: 'posts',
  state: () => ({
    list: [],
  }),
  actions: {
    async fetchPosts($axios) {
      try {
        const response = await $axios.$get('https://api.example.com/posts');
        this.list = response;
      } catch (error) {
        console.error('Failed to fetch posts:', error);
      }
    },
  },
});

Using Pinia Error Handling

Error handling is critical when dealing with asynchronous operations. You can use try-catch blocks to capture and handle errors.

// store/modules/posts.js
import { defineStore } from 'pinia';

export const usePostsStore = defineStore({
  id: 'posts',
  state: () => ({
    list: [],
  }),
  actions: {
    async fetchPosts($axios) {
      try {
        const response = await $axios.$get('https://api.example.com/posts');
        this.list = response;
      } catch (error) {
        console.error('Failed to fetch posts:', error);
        // Trigger a global error notification
        this.$notify.error({
          title: 'Error',
          message: 'Failed to fetch posts.',
        });
      }
    },
  },
});

Using Pinia Performance Optimization

For large applications, performance optimization is crucial. Here are some methods to improve Pinia performance:

  • Reduce Unnecessary Computations: Use computed properties instead of getters if the getter’s computation is costly.
  • Avoid Excessive Rendering: Use watch or computed instead of methods to reduce unnecessary re-renders.
  • Use Debouncing and Throttling: Apply debouncing or throttling techniques to frequent events to reduce unnecessary action calls.
// pages/index.vue
import { useMainStore } from '~/store';

export default {
  setup() {
    const mainStore = useMainStore();

    return {
      count: mainStore.count,
      doubleCount: mainStore.doubleCount,
      increment: _.debounce(mainStore.increment, 300),
    };
  },
};

Using Pinia Debugging Techniques

When debugging a Pinia Store, you can use the following techniques:

  • Use Vue Devtools: Install and use Vue Devtools to inspect and debug the Pinia Store.
  • Log Recording: Add logging in actions and mutations to track state changes.
  • Unit Testing: Write unit tests to verify the Pinia Store’s behavior.

Share your love