DEV Community

Kubilay Çakmak
Kubilay Çakmak

Posted on

Vue.js State Management with Vuex

When using current JavaScript frameworks in an application, we use multiple components (bi). It can be obtained from locations like communicating or exchanging data, as well as the (parent) component it contains or is in. Component counts and productions by apps do not become more difficult in this way. We'll learn about Vuex in this tutorial, which allows us to manage state (state) management for the Vue.js framework from a central location.

What exactly is Vuex?

Vuex is an open-source library for managing Vue.js state from a central location. It is created by Vue.js programmers.

So, what is the point of Vuex? Let's look at an example of code.

Image description

When we look at the code in the image above, we can see that the state value is stored in a Vue instance (instance).

The data (count) that will be used in the programme.
The field (template) where the data from the State will be presented is known as the View.
Action: A structure (increment) that modifies the state in response to a user event.
When we model the above code on the figure, we get something like this. There is a one-way data flow in this image. (data flow in one direction)

Image description

It's a basic structure, but when there are different parts using similar states, this straightforwardness becomes subtle. Different views may need to use similar states. More than one Action may be required to follow similar states. There are ways around the above, but acknowledging them destroys the ease of work, and the application becomes more cluttered. For example: To solve the main problem, it is possible to make a solved part and send the state as a prop to a sub-part, but sending props to a solved design consistently and changing prop values ​​in the sub-part will not give an adaptation.

This and similar situations have led to the centralization of state management; separate state from components, make it a singleton and manage it. In this way, no matter which component is in, you can access the relevant state, trigger any Action, and operate on the same state. That's what Vuex gives us. Ensure state is managed from a central location by decoupling from the application the structure that will change this state.

Vuex Architecture and Core Concepts

Image description

Due to its ease of maintenance and development, Vuex has gathered the state and the structures that will operate on this state in a central place.

All situations seen in the image above are registered in the store in Vuex. Store, in simple terms, is the structure that contains the state of the application.

Although we say that Vuex's store is a global object, there are two main events that distinguish it from global objects.

The Vuex store is reactivity (healthier to know its reactive, non-translational version). When there is any change in the state in the store, the components will be warned and updated effectively.
We cannot directly change the state in the store. It should be clearly stated with the help of Commit (which we will talk about later in the article).
vuex; It consists of 4 basic parts: Actions, Mutations, State, Getters. To see these in more detail, we will go through a basic application.

Let's create an empty project with the help of vue-cli:

vue create vuex-example

Image description

Let's choose the "Default ([Vue 2] babel, eslint)" option since we will show the Vuex implementation for a start.

Install Vuex with npm:

npm install vuex ---save

The implementation of Vuex is quite simple. The steps we need to do are as follows;

We create a JavaScript file called index.js under the store folder. In it, we say that Vue should use Vuex, then we create a new Store instance, define the relevant fields (state, getters, actions, mutations) and export them.
In js, we add it to Vue so that all components can access the store.

Image description

Above, we basically created a Vuex Store object and exported it to be used in main.js. The process of registering this exported store value in main.js is as follows;

Image description

This is the basic implementation.

In the store of Vuex; We talked about the existence of fields such as state, getters, mutations, actions, and we defined them as empty in the implementation part. If we look at the details of these;

State

We mentioned that Vuex maintains a singleton state. All structures in the application use the same state. State data in the application is kept here.

There are many ways to access the values defined in the state in the component.

<template>
  <div id="app">
    {{ count }}
  </div>
</template>

<script>
export default {
  computed: {
    count() {
      return this.$store.state.count;
    },
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

Previously, we performed the bind operation of the store in Vue and we mentioned that the store contains the state. When we look at the code above, we use $store to access the state value and it is located in the Vue instance. With $store.state.count, we can easily access the count data in the state. This process could be done directly in the template, but it would be healthier to do it in computed. When there is any change due to the reactivity of the state, computed will be triggered and the related fields will be updated again.

As the number of data on the state increases, putting the relevant data in computed can be annoying and cause a lot of code. For such problems, we can do it automatically with the mapState that comes in Vuex.

<script>
import { mapState } from "vuex";

export default {
  data() {
    return {
      localCount: 5,
    };
  },
  computed: mapState({
    // arrow function can be used to define it briefly.
    count: (state) => state.count,

    // alias can be given to the relevant state name, `state => state.count` expression corresponds to count.
    countAlias: "count",

    // The relevant local variable can also be accessed with the this keyword using the normal function.
    countPlusLocalState(state) {
      return state.count + this.localCount;
    },
  }),
};
</script>
Enter fullscreen mode Exit fullscreen mode

While making the definitions in the mapState, we can also pass it as an array, just like below.

computed: mapState([
  // Now we can access the value in state.count by saying this.count.
  "count",
])
Enter fullscreen mode Exit fullscreen mode

Instead of typing this.$store.state.count for a long time or bind, we can access that data in the state by typing this.count.

Looking at the above code example, we have assigned the object that mapState returns directly to computed. If we want to use the data in the state as well as to define our own computed properties specifically, we can perform these operations with the spread operator.

computed: {
  // We can specifically create local computed.
  localComputed() {
    /* ... */
  },
  // We can complete the state in the store.
  ...mapState({
    /* ... */
  }),
}
Enter fullscreen mode Exit fullscreen mode

Getters

The structure we call Getter is similar to the computed property in Vue.
To filter the instance when we want to pass a data in the state in the application through certain operations. We can do something like the following.

computed: {
  doneTodosCount() {
    return this.$store.state.todos.filter((todo) => todo.done).length;
  },
},
Enter fullscreen mode Exit fullscreen mode

When we look at the code above, the number of completed ones in the todos array in the state is brought, but when we want to use the result of the filtering process in a few components, we need to copy the above code and put it in other components. In such scenarios, the Getter structure provided by Vuex is used.
When we look at the definition of getter, it takes 2 arguments and the first of these arguments is the state value and the second is the getters where the other getters are located.
**
Below are the **state
and getters defined in store/index.js.

export const store = new Vuex.Store({
    state: {
        todos: [
            { id: 1, text: "...", done: true },
            { id: 2, text: "...", done: false },
        ],
    },
    getters: {
        doneTodosCount: (state, getters) => {
            return state.todos.filter((todo) => todo.done).length;
        },
    },
});
Enter fullscreen mode Exit fullscreen mode

There are similar ways for the related getter to be called in the component, just like in the state.

For direct access via Store in Component (template):

this.$store.getters.doneTodosCount; // -> 1

Usage in Computed:

computed: {
  doneTodosCount() {
    return this.$store.getters.doneTodosCount;
  },
},
Enter fullscreen mode Exit fullscreen mode

We can do the practical mapping for Getters just like we do using mapState in State. For this, we use mapGetters.

<template>
  <div id="app">
    {{ doneCount }}
  </div>
</template>

<script>
import { mapGetters } from "vuex";

export default {
  computed: {
    ...mapGetters({
      // map `this.doneCount` to `this.$store.getters.doneTodosCount`
      doneCount: "doneTodosCount",
    }),
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

An alias has been given to the relevant Getter above. If we want to map directly, we can use it as follows.

<template>
  <div id="app">
    {{ doneTodosCount }}
  </div>
</template>

<script>
import { mapGetters } from "vuex";

export default {
  computed: {
    ...mapGetters(["doneTodosCount", "anotherGetter"]),
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

Mutations

We use structures called mutations to update the data in the state. Each mutation here contains 2 structures, namely handler and type. The field we call Type is the method name, and the handler is the method that will update the relevant state. This method takes 2 parameters and the first parameter is state and the other parameter is data.

Below are the state and mutations defined in store/index.js.

export const store = new Vuex.Store({
    state: {
        count: 0,
    },
    mutations: {
        increment(state) {
            // mutate state
            state.count++;
        },
    },
});
Enter fullscreen mode Exit fullscreen mode

A mutation can be seen above, and its operation is to increase the count in the state by one. Unfortunately, as in State and Getters, direct access is not available in Mutations. To realize this, it is necessary to declare with commit.

Access from within the Vue component is as follows.

this.$store.commit("increment");

A mutation has been triggered above, if data is also wanted to be sent as a parameter, it is sent as the second parameter.

export const store = new Vuex.Store({
    state: {
        count: 0,
    },
    mutations: {
        increment(state, payload) {
            // mutate state
            state.count += payload.amount;
        },
    },
});
Enter fullscreen mode Exit fullscreen mode

Vuex documentation suggests sending the payload as an object.

this.$store.commit("increment", { amount: 4 });

We use mapMutations to perform practical mapping operations within the Component.

<template>
  <div id="app">
    <h1>
      {{ this.$store.state.count }}
    </h1>
    <button @click="increment">Up
  </div>
</template>

<script>
import { mapMutations } from "vuex";

export default {
  methods: {
    ...mapMutations([
      "increment", // map `this.increment()` to `this.$store.commit('increment')`

      // `mapMutations supports payload:
      "incrementBy", // map `this.incrementBy(amount)` to `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: "increment", // map `this.add()` to `this.$store.commit('increment')`
    }),
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

As seen above, we can get the same name as a direct array, by giving alias.

Actions

Actions and Mutations are similar constructs, but there are important differences between them. Because of this difference, the place of use is very important.
The most important difference; action supports asynchronous operation. It is often used in API calls.

Below are the state, mutations and actions defined in store/index.js.

export const store = new Vuex.Store({
    state: {
        todos: [],
    },
    mutations: {
        insertTodos(state, payload) {
            state.todos = payload;
        },
    },
    actions: {
        fetchTodos(context) {
            fetch("https://jsonplaceholder.typicode.com/todos")
                .then((response) => response.json())
                .then((data) => {
                    context.commit("insertTodos", data);
                });
        },
    },
});
Enter fullscreen mode Exit fullscreen mode

In the above example, a method named fetchTodos is defined, and it receives the todo list by requesting the relevant place and triggers the mutation to update the state. In this way, the data coming with the Action will update the State related to the Mutation and the relevant fields will be updated as a component update.

Methods defined in Action take a parameter called context. Context in itself; It contains features such as state, getters, commit, dispatch. Depending on the situation, the appropriate process can be used.

The call of the defined action in the component is carried out with the dispatch operation.

<script>
export default {
  created() {
    this.$store.dispatch("fetchTodos");
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

We touched on many concepts above, to summarize the process of the work:

The relevant action is triggered by dispatch, API request is made and data is received.
Mutation is used to update the value in the state with the data coming in the action, and the commit is made.
It updates the relevant Mutation state value and the Getter using that state is triggered and the component update using that Getter.
To give a general example that includes these,
Below are the state, getters, mutations and actions defined in store/index.js.

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export const store = new Vuex.Store({
    state: {
        todos: [],
    },
    getters: {
        getCompletedTodos(state) {
            return state.todos.filter((todo) => todo.completed);
        },
    },
    mutations: {
        insertTodos(state, payload) {
            state.todos = payload;
        },
    },
    actions: {
        fetchTodos(context) {
            fetch("https://jsonplaceholder.typicode.com/todos")
                .then((response) => response.json())
                .then((data) => {
                    context.commit("insertTodos", data);
                });
        },
    },
});
Enter fullscreen mode Exit fullscreen mode

There is an action called fetchTodos above, it takes the data with the API request and triggers the relevant mutation with commit, our method here is insertTodos. Mutation, on the other hand, updates the state, and due to this update, the components using the getCompletedTodos Getter use the relevant current data as an update.

<template>
  <div id="app">
    <ul>
      <li v-for="todo in getCompletedTodos" :key="todo.id">
        {{ todo.title }}
      </li>
    </ul>
  </div>
</template>

<script>
import { mapActions, mapGetters } from "vuex";

export default {
  methods: {
    ...mapActions(["fetchTodos"]),
  },
  computed: {
    ...mapGetters(["getCompletedTodos"]),
  },
  created() {
    this.fetchTodos();
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

Above is the mapping, use and listing of the related transactions.

So far, we have learned what components Vuex consists of, what conveniences it provides and how it is used.

More information about the state management process is more readable, maintainable (moving to modular structure) and other details is in its official documentation.

Resources:

VueJs

Top comments (0)