Editor’s note: This post was reviewed and updated in August 2021 to include new information.
The composition API in Vue 3 comes with features such as the Provide/Inject API and a new reactive system that lets us create independent reactive variables outside a component and import it into any component we want to use it in.
Both of these could do the work of Vuex, but they aren’t enough to replace Vuex completely. Let’s take a look at why Vuex is is still necessary, even in Vue 3.
First, Vuex offers advanced debugging capabilities that Vue 3 features does not. This is a huge bonus for developers, since no one wants to spend all that time debugging!
Secondly, Vuex has plugins that extend its capabilities. The functionality of most plugins could be replicated with the Composition API, but Vuex does this better and with a more organized structure.
The short answer is: Yes. Vuex is the preferred state management solution for Vue apps, and Vuex 4 is the version compatible with Vue 3.
Do take note, however, that using Vuex for state management depends on the complexity of your application. In a small application, Vuex would be overkill, and in such a situation, the Provide/Inject API or the Composition API’s reactive features would be a better option. You can read more about when and when not to use Vuex if you need further information.
In this tutorial, we’ll explore how to use Vuex 4 with Vue 3.
We can install Vuex with Vue 3 in a few ways, one of which is to use the script tag.
To use the script tag method, we can write:
<!DOCTYPE html> <html lang="en"> <head> <script src="https://unpkg.com/vue@next"></script> <script src="https://unpkg.com/[email protected]/dist/vuex.global.js"></script> <title>App</title> </head> <body> <div id="app"> <button>increment</button> <p></p> </div> <script> const app = Vue.createApp(); app.mount("#app"); </script> </body> </html>
Above, we add the scripts for Vuex 4 and Vue, and then we can use the Vuex global object in our code.
A Vuex store
is an object that wraps all of our application’s state and enables us access to features such as mutations, actions, and getters to use in our components to access or update the global state.
To create a store, we call the Vuex.Store
constructor with an object including the states and mutations that we want to add to create a basic store.
States
are properties that store data in our Vuex store; they let us access the data from anywhere in our Vue 3 app.
Once we’ve created our store, we pass it into the Vue.createApp
method to add the store to our app. The app.use(store);
method call lets us use the store in our Vue 3 app.
Then, we define our application’s state, which is count
in this case, and also create the mutation increment
so we can invoke it to update the value of count
. Mutations are functions that let us modify states in the Vuex store.
We can then use the this.$store
property to get the states and manipulate our store. The this.$store.commit
method lets us commit the mutations to the store.
We commit the increment
mutation in our store to update the count
state. Therefore, when we click the increment button in our template code, the count
Vuex state will be updated along with the count
computed property.
Below is what our code should look like:
<!DOCTYPE html> <html lang="en"> <head> <script src="https://unpkg.com/vue@next"></script> <script src="https://unpkg.com/[email protected]/dist/vuex.global.js"></script> <title>App</title> </head> <body> <div id="app"> <button @click="increment">increment</button> <p>{{count}}</p> </div> <script> const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment(state) { state.count++; } } }); const app = Vue.createApp({ methods: { increment() { this.$store.commit("increment"); } }, computed: { count() { return this.$store.state.count; } } }); app.use(store); app.mount("#app"); </script> </body> </html>
To add store states into our app more easily, we can use getters. Getters are functions that return a state, or states that have been operated on or combined with other values.
For example, we can add a getter by writing:
<!DOCTYPE html> <html lang="en"> <head> <script src="https://unpkg.com/vue@next"></script> <script src="https://unpkg.com/[email protected]/dist/vuex.global.js"></script> <title>App</title> </head> <body> <div id="app"> <button @click="increment">increment</button> <p>{{doubleCount}}</p> </div> <script> const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment(state) { state.count++; } }, getters: { doubleCount: (state) => { return state.count * 2; } } }); const app = Vue.createApp({ methods: { increment() { this.$store.commit("increment"); } }, computed: { ...Vuex.mapGetters(["doubleCount"]) } }); app.use(store); app.mount("#app"); </script> </body> </html>
We added the doubleCount
getter, which returns the count
state, multiplied by two. Then, in the component, we call the Vuex.mapGetters
method with the name of the getter to map it to a computed property. We also have it in the template so we can see its value.
If we want a method as a getter, we can return a function in our getter. For instance, we can write:
<!DOCTYPE html> <html lang="en"> <head> <script src="https://unpkg.com/vue@next"></script> <script src="https://unpkg.com/[email protected]/dist/vuex.global.js"></script> <title>App</title> </head> <body> <div id="app"> <div> <p>{{getTodoById(1).text}}</p> </div> </div> <script> const store = new Vuex.Store({ state: { todos: [ { id: 1, text: "drink", done: true }, { id: 2, text: "sleep", done: false } ] }, getters: { getTodoById: (state) => (id) => { return state.todos.find((todo) => todo.id === id); } } }); const app = Vue.createApp({ computed: { ...Vuex.mapGetters(["getTodoById"]) } }); app.use(store); app.mount("#app"); </script> </body> </html>
We have the todos
Vuex store state, and we want to get an entry from it by its id
property value. To do that, we have the getTodosById
getter method, which returns a function. The function subsequently returns the entry from the state.todos
array by calling find
to get the value by its id
value.
In the component, we call Vuex.mapGetters
the same way to map the method to a computed property. Then, we can call the function it returns to get the to-do item by its id
value. Therefore, 'drink'
should be displayed on the browser screen since this has id: 1
.
We’ve already seen a mutation in the previous example; it’s just a method we can use to modify a state.
A mutation method can take a payload, which can be used to modify a state value. For example, we can write:
<!DOCTYPE html> <html lang="en"> <head> <script src="https://unpkg.com/vue@next"></script> <script src="https://unpkg.com/[email protected]/dist/vuex.global.js"></script> <title>App</title> </head> <body> <div id="app"> <button @click="increment">increment</button> <p>{{count}}</p> </div> <script> const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment(state, n) { state.count += n; } } }); const app = Vue.createApp({ methods: { increment() { this.$store.commit("increment", 5); } }, computed: { count() { return this.$store.state.count; } } }); app.use(store); app.mount("#app"); </script> </body> </html>
Our increment
mutation method has an n
parameter that is used to increase the value of the count
state. Then, we call the this.$store.commit
method with a second argument to pass in the value to the increment
method. n
should now be 5
, so the count
Vuex state would be incremented by five.
In some cases where we might need to pass more than one data property to update our Vuex state, we can take advantage of object style commits. It also helps to make our commits more descriptive.
For example, we can write:
<!DOCTYPE html> <html lang="en"> <head> <script src="https://unpkg.com/vue@next"></script> <script src="https://unpkg.com/[email protected]/dist/vuex.global.js"></script> <title>App</title> </head> <body> <div id="app"> <button @click="increment">increment</button> <p>{{count}}</p> </div> <script> const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment(state, { amount }) { state.count += amount; } } }); const app = Vue.createApp({ methods: { increment() { this.$store.commit({ type: "increment", amount: 5 }); } }, computed: { count() { return this.$store.state.count; } } }); app.use(store); app.mount("#app"); </script> </body> </html>
In this snippet, we called this.$store.commit
with an object with the type
and amount
properties.
The type
property is used to find the name of the mutation method to call. So, we’ll call the increment
mutation method since that’s the value of type
. The other properties will be passed in with the object that we pass as the second argument of the increment
method.
So we get the amount
property from the second parameter of increment
and use that to update the Vuex store’s count
state.
Mutations do have some limitations. Mutation methods must be synchronous so that their order of execution can be tracked with Vuex. However, Vuex has action methods that let us run mutations to modify a state.
Actions can run any kind of code, including async ones. For instance, we can write:
<!DOCTYPE html> <html lang="en"> <head> <script src="https://unpkg.com/vue@next"></script> <script src="https://unpkg.com/[email protected]/dist/vuex.global.js"></script> <title>App</title> </head> <body> <div id="app"> <p>{{answer}}</p> </div> <script> const store = new Vuex.Store({ state: { answer: "" }, mutations: { setAnswer(state, answer) { state.answer = answer; } }, actions: { async getAnswer(context) { const res = await fetch("https://yesno.wtf/api"); const answer = await res.json(); context.commit("setAnswer", answer); } } }); const app = Vue.createApp({ mounted() { this.$store.dispatch("getAnswer"); }, computed: { answer() { return this.$store.state.answer; } } }); app.use(store); app.mount("#app"); </script> </body> </html>
This will add the setAnswer
action, which is added as a method of the actions
property. The context
parameter has the commit
method that lets us commit mutations. The argument is the name of the mutation.
The getAnswer
action method is async, and it calls the context.commit
method to commit the setAnswer
mutation. The answer
in the second argument is passed into the setAnswer
method as the value of the answer
parameter, and it’s set as the value of the state.answer
property.
Then, in the component, we can get the answer
by using the this.$store.state.answer
property. In the mounted
hook, we call this.$store.dispatch("getAnswer");
to dispatch the getAnswer
action. Therefore, we should see an object with the answer in the template.
Vuex 4 is not terribly different from previous versions of Vuex; the v4 update is mainly for compatibility purposes with Vue 3.
It has the same parts, like getters, mutations, and actions, which are used to get and set Vuex store states.
Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps, including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error and what state the application was in when an issue occurred.
Modernize how you debug your Vue apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
2 Replies to "Using Vuex 4 with Vue 3"
Thank you for this very nice and clear article.
I think that there is a typo:
“In a large application, Vuex would be overkill,” => ‘In a small application, Vuex would be overkill,”
Hi Emile, thanks for catching that! We’ve updated the sentence now. We’re glad you enjoyed the article!