Vuex Store

Using a store to manage the state is important for every big application. That's why Nuxt.js implements Vuex in its core.

Activate the Store

Nuxt.js will look for the store directory, if it exists, it will:

  • Import Vuex,
  • Add the store option to the root Vue instance. Nuxt.js lets you decide between 2 store modes. You can choose the one you prefer:

  • Modules: every .js file inside the store directory is transformed as a namespaced module (index being the root module).

  • Classic (deprecated): store/index.js returns a method to create a store instance. Regardless of the mode, your state value should always be a function to avoid unwanted shared state on the server side.

Modules mode

Nuxt.js lets you have a store directory with every file corresponding to a module.

To get started, simply export the state as a function, and the mutations and actions as objects in store/index.js:

  1. export const state = () => ({
  2. counter: 0
  3. })
  4. export const mutations = {
  5. increment (state) {
  6. state.counter++
  7. }
  8. }

Then, you can have a store/todos.js file:

  1. export const state = () => ({
  2. list: []
  3. })
  4. export const mutations = {
  5. add (state, text) {
  6. state.list.push({
  7. text: text,
  8. done: false
  9. })
  10. },
  11. remove (state, { todo }) {
  12. state.list.splice(state.list.indexOf(todo), 1)
  13. },
  14. toggle (state, todo) {
  15. todo.done = !todo.done
  16. }
  17. }

The store will be created as such:

  1. new Vuex.Store({
  2. state: () => ({
  3. counter: 0
  4. }),
  5. mutations: {
  6. increment (state) {
  7. state.counter++
  8. }
  9. },
  10. modules: {
  11. todos: {
  12. namespaced: true,
  13. state: () => ({
  14. list: []
  15. }),
  16. mutations: {
  17. add (state, { text }) {
  18. state.list.push({
  19. text,
  20. done: false
  21. })
  22. },
  23. remove (state, { todo }) {
  24. state.list.splice(state.list.indexOf(todo), 1)
  25. },
  26. toggle (state, { todo }) {
  27. todo.done = !todo.done
  28. }
  29. }
  30. }
  31. }
  32. })

And in your pages/todos.vue, using the todos module:

  1. <template>
  2. <ul>
  3. <li v-for="todo in todos">
  4. <input type="checkbox" :checked="todo.done" @change="toggle(todo)">
  5. <span :class="{ done: todo.done }">{{ todo.text }}</span>
  6. </li>
  7. <li><input placeholder="What needs to be done?" @keyup.enter="addTodo"></li>
  8. </ul>
  9. </template>
  10. <script>
  11. import { mapMutations } from 'vuex'
  12. export default {
  13. computed: {
  14. todos () {
  15. return this.$store.state.todos.list
  16. }
  17. },
  18. methods: {
  19. addTodo (e) {
  20. this.$store.commit('todos/add', e.target.value)
  21. e.target.value = ''
  22. },
  23. ...mapMutations({
  24. toggle: 'todos/toggle'
  25. })
  26. }
  27. }
  28. </script>
  29. <style>
  30. .done {
  31. text-decoration: line-through;
  32. }
  33. </style>

The module method also works for top-level definitions without implementing a sub-directory in the store directory

Example for state: you create a file store/state.js and add the following

  1. export default () => ({
  2. counter: 0
  3. })

And the corresponding mutations can be in the file store/mutations.js

  1. export default {
  2. increment (state) {
  3. state.counter++
  4. }
  5. }

Module files

You can optionally break down a module file into separate files: state.js, actions.js, mutations.js and getters.js. If you maintain an index.js file with state, getters and mutations while having a single separate file for actions, that will also still be properly recognized.

Note: Whilst using split-file modules, you must remember that using arrow functions, this is only lexically available. Lexical scoping simply means that the this always references the owner of the arrow function. If the arrow function is not contained then this would be undefined. The solution is to use a "normal" function which produces its own scope and thus has this available.

Plugins

You can add additional plugins to the store (in the modules mode) by putting them into the store/index.js file:

  1. import myPlugin from 'myPlugin'
  2. export const plugins = [ myPlugin ]
  3. export const state = () => ({
  4. counter: 0
  5. })
  6. export const mutations = {
  7. increment (state) {
  8. state.counter++
  9. }
  10. }

More information about the plugins: Vuex documentation.

The fetch Method

The fetch method is used to fill the store before rendering the page, it's like the asyncData method except it doesn't set the component data.

More information about the fetch method: API Pages fetch.

The nuxtServerInit Action

If the action nuxtServerInit is defined in the store, Nuxt.js will call it with the context (only from the server-side). It's useful when we have some data on the server we want to give directly to the client-side.

For example, let's say we have sessions on the server-side and we can access the connected user through req.session.user. To give the authenticated user to our store, we update our store/index.js to the following:

  1. actions: {
  2. nuxtServerInit ({ commit }, { req }) {
  3. if (req.session.user) {
  4. commit('user', req.session.user)
  5. }
  6. }
  7. }

If you are using the Modules mode of the Vuex store, only the primary module (in store/index.js) will receive this action. You'll need to chain your module actions from there.

The context is given to nuxtServerInit as the 2nd argument, it is the same as asyncData or fetch method.

Note: Asynchronous nuxtServerInit actions must return a Promise or leverage async/await to allow the nuxt server to wait on them.

  1. actions: {
  2. async nuxtServerInit({ dispatch }) {
  3. await dispatch('core/load')
  4. }
  5. }

Vuex Strict Mode

Strict mode is enabled by default on dev mode and turned off in production mode. To disable strict mode in dev, follow the below example in store/index.js:

export const strict = false

Classic mode

This feature is deprecated and will be removed in Nuxt 3.

To activate the store with the classic mode, we create the store/index.js file which should export a method that returns a Vuex instance:

  1. import Vuex from 'vuex'
  2. const createStore = () => {
  3. return new Vuex.Store({
  4. state: () => ({
  5. counter: 0
  6. }),
  7. mutations: {
  8. increment (state) {
  9. state.counter++
  10. }
  11. }
  12. })
  13. }
  14. export default createStore

We don't need to install vuex since it's shipped with Nuxt.js.

We can now use this.$store inside our components:

  1. <template>
  2. <button @click="$store.commit('increment')">{{ $store.state.counter }}</button>
  3. </template>