Vuex, the Vue.js State Manager
By Flavio Copes
Learn how to use Vuex 4 with Vue 3 to centralize your app state with a store, mutations, getters and commit, and when to pick Pinia instead.
- Introduction to Vuex
- Vuex or Pinia?
- Why should you use a store
- Let’s start
- Create the Vuex store
- A use case for the store
- Introducing the new components we need
- Adding those components to the app
- Add the state to the store
- Add a mutation
- Add a getter to reference a state property
- Adding the Vuex store to the app
- Update the state on a user action using commit
- Use the getter to print the state value
- Wrapping up
Introduction to Vuex
Vuex is the state management library made by the Vue team for Vue.js.
Its job is to share data across the components of your application.
This post uses Vuex 4, the version that works with Vue 3. I first wrote it for Vuex 3 and Vue 2, and the concepts are the same. Only the setup code changed.
Vuex or Pinia?
Vuex 4 (4.1.0 as of September 2026) still works fine with Vue 3, and it is still maintained, but it will not get new features. The Vue team now recommends Pinia as the official store for new projects. The Vuex docs describe Pinia as “Vuex 5 with a different name”: no mutations, just state, getters and actions.
So why learn Vuex? Because a huge number of existing Vue apps use it, and because everything you learn here (a central store, getters, changing state through a defined channel) carries over to Pinia. If you are starting a brand new app, read this post to understand the pattern, then pick Pinia.
Components in Vue.js out of the box can communicate using
- props, to pass state down to child components from a parent
- events, to change the state of a parent component from a child, or using the root component as an event bus
Sometimes things get more complex than what these simple options allow.
In this case, a good option is to centralize the state in a single store. This is what Vuex does.
Why should you use a store
Vuex is not the only state management option you can use in Vue (you can use Redux too), but its main advantage is that it comes from the Vue team, and its integration with Vue.js is what makes it shine. The same goes for Pinia.
With React you have the trouble of having to choose one of the many libraries available, as the ecosystem is huge and has no de-facto standard. Lately Redux was the most popular choice, with MobX following up in terms of popularity. With Vue I’d go as far as to say that you won’t need to look around for anything other than Vuex or Pinia, especially when starting out.
Vuex borrowed many of its ideas from the React ecosystem, as this is the Flux pattern popularized by Redux.
If you know Flux or Redux already, Vuex will be very familiar. If you don’t, no problem - I’ll explain every concept from the ground up.
Components in a Vue application can have their own state. For example, an input box will store the data entered into it locally. This is perfectly fine, and components can have local state even when using Vuex.
You know that you need something like Vuex when you start doing a lot of work to pass a piece of state around.
In this case Vuex provides a central repository store for the state, and you mutate the state by asking the store to do that.
Every component that depends on a particular piece of the state will access it using a getter on the store, which makes sure it’s updated as soon as that thing changes.
Using Vuex will introduce some complexity into the application, as things need to be set up in a certain way to work correctly, but if this helps solve the unorganized props passing and event system that might grow into a spaghetti mess if too complicated, then it’s a good choice.
Let’s start
In this example I’m starting from a Vue 3 app created with npm create vue@latest, which uses Vite. Vuex can be loaded from a script tag too, but since Vuex is more in tune with bigger applications, it’s much more likely you will use it on a structured, npm-based project like this one.
You can also follow along on CodeSandbox, which is a great service that has Vue templates ready to go. I recommend using it to play around. Once you’re there, add the vuex dependency.
To install Vuex locally you can run npm install vuex@4 inside the project folder. The @4 is not strictly needed, since 4.1.0 is the latest release, but it makes clear which line we want.
Create the Vuex store
Now we are ready to create our Vuex store.
This file can be put anywhere. It’s generally suggested to put it in the src/store/store.js file, so we’ll do that.
In this file we create the store with createStore:
import { createStore } from 'vuex'
export const store = createStore({})
We export a Vuex store object, which we create using the createStore() API. In Vuex 3 this was new Vuex.Store() plus Vue.use(Vuex). Vuex 4 dropped both.
A use case for the store
Now that we have a skeleton in place, let’s come up with an idea for a good use case for Vuex, so I can introduce its concepts.
For example, I have 2 sibling components, one with an input field, and one that prints that input field content.
When the input field is changed, I want to also change the content in that second component. Very simple but this will do the job for us.
Introducing the new components we need
I delete the HelloWorld component and add a Form component, and a Display component.
<template>
<div>
<label for="flavor">Favorite ice cream flavor?</label>
<input name="flavor">
</div>
</template>
<template>
<div>
<p>You chose ???</p>
</div>
</template>
Adding those components to the app
We add them to the App.vue code instead of the HelloWorld component:
<template>
<div id="app">
<Form/>
<Display/>
</div>
</template>
<script>
import Form from './components/Form.vue'
import Display from './components/Display.vue'
export default {
name: 'App',
components: {
Form,
Display
}
}
</script>
Add the state to the store
So with this in place, we go back to the store.js file and we add a property to the store called state. It’s a function that returns an object, and that object contains the flavor property. That’s an empty string initially.
import { createStore } from 'vuex'
export const store = createStore({
state() {
return {
flavor: ''
}
}
})
We’ll update it when the user types into the input field.
Add a mutation
The state cannot be manipulated except by using mutations. We set up one mutation which will be used inside the Form component to notify the store that the state should change.
import { createStore } from 'vuex'
export const store = createStore({
state() {
return {
flavor: ''
}
},
mutations: {
change(state, flavor) {
state.flavor = flavor
}
}
})
Add a getter to reference a state property
With that set, we need to add a way to look at the state. We do so using getters. We set up a getter for the flavor property:
import { createStore } from 'vuex'
export const store = createStore({
state() {
return {
flavor: ''
}
},
mutations: {
change(state, flavor) {
state.flavor = flavor
}
},
getters: {
flavor: state => state.flavor
}
})
Notice how getters is an object. flavor is a property of this object, which accepts the state as the parameter, and returns the flavor property of the state. For a plain pass-through you could also read store.state.flavor directly. Getters shine when you compute something from state.
Adding the Vuex store to the app
Now the store is ready to be used. We go back to our application code, and in main.js we import the store and install it on the app with app.use():
import { createApp } from 'vue'
import App from './App.vue'
import { store } from './store/store'
const app = createApp(App)
app.use(store)
app.mount('#app')
Once we add this, every component in the app can reach the store as this.$store (Options API) or through the useStore() function (Composition API).
Update the state on a user action using commit
Let’s update the state when the user types something.
We do so by using the store.commit() API.
But first, let’s create a method that is invoked when the input content changes. We use @input rather than @change because the latter is only triggered when the focus is moved away from the input box, while @input is called on every keypress.
<template>
<div>
<label for="flavor">Favorite ice cream flavor?</label>
<input @input="changed" name="flavor">
</div>
</template>
<script>
export default {
methods: {
changed: function(event) {
alert(event.target.value)
}
}
}
</script>
Now that we have the value of the flavor, we use the Vuex API:
<script>
export default {
methods: {
changed: function(event) {
this.$store.commit('change', event.target.value)
}
}
}
</script>
See how we reference the store using this.$store? This is thanks to app.use(store) in main.js.
The commit() method accepts a mutation name (we used change in the Vuex store) and a payload, which will be passed to the mutation as the second parameter of its callback function.
With <script setup> there is no this, so you get the store with useStore():
<script setup>
import { useStore } from 'vuex'
const store = useStore()
function changed(event) {
store.commit('change', event.target.value)
}
</script>
Use the getter to print the state value
Now we need to reference the getter of this value in the Display template, by using $store.getters.flavor. this can be removed because we’re in the template, and this is implicit.
<template>
<div>
<p>You chose {{ $store.getters.flavor }}</p>
</div>
</template>
$store is available in every template once the store is installed, whether the component uses the Options API or <script setup>.
Wrapping up
That’s it for an introduction to Vuex!
There are still many concepts missing in this puzzle:
- actions
- modules
- helpers like
mapStateandmapGetters - plugins
but you have the basics to go and read about them in the official docs. And if you decide to go with Pinia instead, the Pinia docs have a migration guide from Vuex.
Happy coding!
Want me to talk about your product? You can sponsor this site.