Things are a-changing in the Vueniverse - as are our approaches to state management. In the past, Vuex was the default choice for state management (though not always the most loved one). Now you’ll see other solutions being recommended.
Vue 3 simplifies a lot of things, and new solutions are great. But we know it can get confusing to keep up with best practices. What to (not) use now?
An alternative title for this article was „Vue 3 state management for dummies“. We know you’re not a dummy, but consider this your executive summary of current state management concepts and resources for Vue 3.
To display an app’s interface, you need a lot of information about its state: Should this checkbox be checked? Was that form submission successful?
When the user interacts with the app, this state information often needs to be used by multiple different components. Think about a user putting a product into their shopping cart in an e-commerce app: This state probably needs to be reflected on the product page („added to cart!“) and in the cart components (changed product count in the cart and added product in the list).
Saving and accessing this data in a centralized, structured way (instead of passing it around from one component to another) is what we typically mean when we talk about state management.
The store we use for this is sometimes likened to a frontend database. It makes sure you have only one single source of truth for the data. You can access it from everywhere to use in different contexts. Easier to work with, easier to maintain, less bug-prone!
Whether you need a state management solution depends on the complexity and structure of your app. It's not really about its size, but larger apps tend to grow more complex as well. Do multiple of your components need to react and change when a user interacts with your app? That’s a good indicator that you should think about state management.
Before we get into libraries you can use to manage your application's state: Do we even need a dedicated state management library with Vue 3? An intensely discussed topic these days!
As Vue matured, it got progressively easier to manage state without a state management library. With the introduction of Vue Observables in Vue 2.6, we already could set up a homemade store by creating a reactive object that holds our shared application state. Now the Composition API and its globally available reactive objects make it even easier to share state and stateful logic between components.
You can find an example of a simple state management pattern in the Vue 3 docs. Here are some additional tutorials and articles about how you could go about writing your own store:
Creating your own store could be interesting for you if you’re looking for a (really) flexible solution. What you’ll be missing is the possibility to debug your store with Vue Devtools. Also, if you’re deciding on a state management solution for large projects (and teams), you might still prefer the standardization of a state management library.
Pinia went from experimental approach for Vue 3 by core team member Eduardo San Martin Morote to popular library quite fast.
<div class="brick__image-inner "
id="js-image-2679"
v-lazy-bg="'https://madewithnetworkfra.fra1.digitaloceanspaces.com/spatie-space-production/27735/pinia-2.jpg'"
style=" ">
</div>
</div>
<div class="brick__caption">
<div class="brick__caption-upper">
<a href="https://madewithvuejs.com/pinia" class="brick__title">
Pinia
</a>
<span class="brick__tagline">
Vue Store
</span>
</div>
<div class="brick__caption-lower">
<div class="pull-left">
<a href="/utilities">#Utilities</a>
<a href="/state-management">#State management</a>
</div>
<div class="pull-right">
<div class="brick__views">
<svg class="svg-inline u__va--middle" width="20px" height="13px" viewBox="0 0 20 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>icon-eye-dark</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Icons" transform="translate(-313.000000, -230.000000)" fill-rule="nonzero" fill="#435B71">
<g id="icon-eye-dark" transform="translate(313.000000, 230.000000)">
<path d="M9.57827924,0.0932647368 C5.40166592,0.0977121053 2.23909537,3.28527789 0.162366374,6.09013053 C-0.0164829778,6.33193046 -0.0164829778,6.66216481 0.162366374,6.90396474 C2.23923803,9.71401737 5.40085034,12.9011726 9.57827924,12.9068174 C9.57872397,12.9068178 9.57916871,12.9068178 9.57961345,12.9068174 C13.7562192,12.90237 16.9187977,9.71477 18.9955264,6.90995158 C19.1743758,6.66815165 19.1743758,6.3379173 18.9955264,6.09611737 C16.9186536,3.28603053 13.7570499,0.0989094737 9.57961345,0.0932647368 C9.57916871,0.0932643033 9.57872397,0.0932643033 9.57827924,0.0932647368 L9.57827924,0.0932647368 Z M9.57827924,1.46168579 L9.57961345,1.46168579 C12.8675916,1.46681737 15.5988249,3.96846211 17.5569468,6.50236737 C15.5985806,9.03230421 12.8660264,11.5341884 9.57961345,11.5383963 C6.29104758,11.5339489 3.55933568,9.03192789 1.60094367,6.49771474 C3.55930832,3.96777789 6.29185563,1.46589368 9.57827718,1.46168579 L9.57827924,1.46168579 Z" id="Shape"></path>
<path d="M9.57894737,3.42075158 C7.88659608,3.42075158 6.5,4.80737263 6.5,6.49969895 C6.5,8.19205947 7.88659608,9.57864632 9.57894737,9.57864632 C11.2712987,9.57864632 12.6578947,8.19205947 12.6578947,6.49969895 C12.6578947,4.80737263 11.2712987,3.42075158 9.57894737,3.42075158 Z M9.57894737,4.78917263 C10.5317492,4.78917263 11.2894737,5.54690158 11.2894737,6.49969895 C11.2894737,7.45249632 10.5317492,8.21022526 9.57894737,8.21022526 C8.62614555,8.21022526 7.86842105,7.45249632 7.86842105,6.49969895 C7.86842105,5.54690158 8.62614555,4.78917263 9.57894737,4.78917263 Z" id="Shape"></path>
</g>
</g>
</g>
0
</div>
Its name means pineapple in Spanish (piña), which is a nod to the core concept of modular, connected stores – because a pineapple is actually not one fruit, but multiple fruits merged together. (Did you know?)
The lightweight library (1KB) is straightforward to use, and takes away a lot of the complexity devs dislike about Vuex, the previously officially recommended Vue state management solution. No mutations (functions that are used to change state), no complicated nested structures, automatic namespacing and code splitting, proper TypeScript support. Pinia is fully extensible, so you can create plugins on top of it and it also supports server-side rendering (SSR) and debugging with Vue Devtools.
Pinia has initially been an experiment to test this approach for the development of Vuex 5 (remember, it has been created by a core team member!). As it got so popular, it’s no surprise that this is also the direction Vuex will be going.
Here are some resources to get you started:
Vuex is the state management library officially maintained by the Vue core team. It does not have a reputation of being easy to use, but that’s mainly because of trade-offs needed for being a powerful solution.
<div class="brick__image-inner "
id="js-image-1547"
v-lazy-bg="'https://madewithnetworkfra.fra1.digitaloceanspaces.com/spatie-space-production/3323/vuex.jpg'"
style=" ">
</div>
</div>
<div class="brick__caption">
<div class="brick__caption-upper">
<a href="https://madewithvuejs.com/vuex" class="brick__title">
Vuex
</a>
<span class="brick__tagline">
Centralized State Management
</span>
</div>
<div class="brick__caption-lower">
<div class="pull-left">
<a href="/utilities">#Utilities</a>
<a href="/state-management">#State management</a>
</div>
<div class="pull-right">
<div class="brick__views">
<svg class="svg-inline u__va--middle" width="20px" height="13px" viewBox="0 0 20 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>icon-eye-dark</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Icons" transform="translate(-313.000000, -230.000000)" fill-rule="nonzero" fill="#435B71">
<g id="icon-eye-dark" transform="translate(313.000000, 230.000000)">
<path d="M9.57827924,0.0932647368 C5.40166592,0.0977121053 2.23909537,3.28527789 0.162366374,6.09013053 C-0.0164829778,6.33193046 -0.0164829778,6.66216481 0.162366374,6.90396474 C2.23923803,9.71401737 5.40085034,12.9011726 9.57827924,12.9068174 C9.57872397,12.9068178 9.57916871,12.9068178 9.57961345,12.9068174 C13.7562192,12.90237 16.9187977,9.71477 18.9955264,6.90995158 C19.1743758,6.66815165 19.1743758,6.3379173 18.9955264,6.09611737 C16.9186536,3.28603053 13.7570499,0.0989094737 9.57961345,0.0932647368 C9.57916871,0.0932643033 9.57872397,0.0932643033 9.57827924,0.0932647368 L9.57827924,0.0932647368 Z M9.57827924,1.46168579 L9.57961345,1.46168579 C12.8675916,1.46681737 15.5988249,3.96846211 17.5569468,6.50236737 C15.5985806,9.03230421 12.8660264,11.5341884 9.57961345,11.5383963 C6.29104758,11.5339489 3.55933568,9.03192789 1.60094367,6.49771474 C3.55930832,3.96777789 6.29185563,1.46589368 9.57827718,1.46168579 L9.57827924,1.46168579 Z" id="Shape"></path>
<path d="M9.57894737,3.42075158 C7.88659608,3.42075158 6.5,4.80737263 6.5,6.49969895 C6.5,8.19205947 7.88659608,9.57864632 9.57894737,9.57864632 C11.2712987,9.57864632 12.6578947,8.19205947 12.6578947,6.49969895 C12.6578947,4.80737263 11.2712987,3.42075158 9.57894737,3.42075158 Z M9.57894737,4.78917263 C10.5317492,4.78917263 11.2894737,5.54690158 11.2894737,6.49969895 C11.2894737,7.45249632 10.5317492,8.21022526 9.57894737,8.21022526 C8.62614555,8.21022526 7.86842105,7.45249632 7.86842105,6.49969895 C7.86842105,5.54690158 8.62614555,4.78917263 9.57894737,4.78917263 Z" id="Shape"></path>
</g>
</g>
</g>
0
</div>
Vuex (up to Vuex 4) is the Vue-specific implementation of the Flux design pattern, originally developed by Facebook. Other popular state management libraries are based upon this architecture as well – like Redux, which is technically framework-agnostic but most commonly used together with React. In this pattern, there’s only one centralized way to update values in your store, making sure it can’t be changed directly (and accidentally).
In Vuex (up to Vuex 4), this results in more code and complexity. You kind of trade simplicity for predictability and prevention of accidental state changes. This is a pretty good trade in many cases!
As for more pros: Vuex has flawless Devtools support, including useful features like time travel debugging. It’s also tailored to fit Vue’s reactivity system, supports SSR and code splitting.
Vuex 4 already made some changes to be easier to use compared to previous versions, but it is mostly an update for Vue 3 support while keeping the same API as Vuex 3. More exciting changes are coming with Vuex 5, which is currently an RFC (Request for Comments) proposal.
As you already read above, Vuex 5 will go the same direction as the Pinia state management library, bringing TypeScript support and removing the need to use mutations and nested modules which caused a big chunk of complexity.
This means it’s moving away from being a Flux library for Vue towards being Vue’s own, extensible solution for global state management. (Note that this probably makes migrating from Vuex 3 or 4 to Vuex 5 costly!)
Hear, see and read more about Vuex 5 here:
When things evolve, they sometimes get confusing for a while. So, Pinia and Vuex are the same? Or will be the same? What to use? To make your confusion complete: The official Vue.js default recommendation for state management is now ... Pinia!
As Evan You pointed out, you can consider Pinia to be Vuex 5.
We don't yet know how these libraries will coexist or be merged exactly, but what started out as an experiment is now a popular library that mirrors what Vuex 5 aims to be. Pinia has the same core API as Vuex 5 and aims to stay compatible in any case.
It’s a nice example how things evolve in the community: Sometimes it's great to move fast and create a new library to test an idea like it happened with Pinia – but for officially supported libraries it's also important to involve the community's opinions (e.g. through the RFC process that Vuex uses). With Pinia and Vuex 5, both approaches support the same solution!
If you want to keep tabs on other approaches to state management by the community, you can take a look at these projects as well:
<div class="brick__image-inner "
id="js-image-2494"
v-lazy-bg="'https://madewithnetworkfra.fra1.digitaloceanspaces.com/spatie-space-production/16946/vue-class-store.jpg'"
style=" ">
</div>
</div>
<div class="brick__caption">
<div class="brick__caption-upper">
<a href="https://madewithvuejs.com/vue-class-store" class="brick__title">
Vue Class Store
</a>
<span class="brick__tagline">
Universal Vue stores you write once and use anywhere
</span>
</div>
<div class="brick__caption-lower">
<div class="pull-left">
<a href="/utilities">#Utilities</a>
<a href="/state-management">#State management</a>
</div>
<div class="pull-right">
<div class="brick__views">
<svg class="svg-inline u__va--middle" width="20px" height="13px" viewBox="0 0 20 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>icon-eye-dark</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Icons" transform="translate(-313.000000, -230.000000)" fill-rule="nonzero" fill="#435B71">
<g id="icon-eye-dark" transform="translate(313.000000, 230.000000)">
<path d="M9.57827924,0.0932647368 C5.40166592,0.0977121053 2.23909537,3.28527789 0.162366374,6.09013053 C-0.0164829778,6.33193046 -0.0164829778,6.66216481 0.162366374,6.90396474 C2.23923803,9.71401737 5.40085034,12.9011726 9.57827924,12.9068174 C9.57872397,12.9068178 9.57916871,12.9068178 9.57961345,12.9068174 C13.7562192,12.90237 16.9187977,9.71477 18.9955264,6.90995158 C19.1743758,6.66815165 19.1743758,6.3379173 18.9955264,6.09611737 C16.9186536,3.28603053 13.7570499,0.0989094737 9.57961345,0.0932647368 C9.57916871,0.0932643033 9.57872397,0.0932643033 9.57827924,0.0932647368 L9.57827924,0.0932647368 Z M9.57827924,1.46168579 L9.57961345,1.46168579 C12.8675916,1.46681737 15.5988249,3.96846211 17.5569468,6.50236737 C15.5985806,9.03230421 12.8660264,11.5341884 9.57961345,11.5383963 C6.29104758,11.5339489 3.55933568,9.03192789 1.60094367,6.49771474 C3.55930832,3.96777789 6.29185563,1.46589368 9.57827718,1.46168579 L9.57827924,1.46168579 Z" id="Shape"></path>
<path d="M9.57894737,3.42075158 C7.88659608,3.42075158 6.5,4.80737263 6.5,6.49969895 C6.5,8.19205947 7.88659608,9.57864632 9.57894737,9.57864632 C11.2712987,9.57864632 12.6578947,8.19205947 12.6578947,6.49969895 C12.6578947,4.80737263 11.2712987,3.42075158 9.57894737,3.42075158 Z M9.57894737,4.78917263 C10.5317492,4.78917263 11.2894737,5.54690158 11.2894737,6.49969895 C11.2894737,7.45249632 10.5317492,8.21022526 9.57894737,8.21022526 C8.62614555,8.21022526 7.86842105,7.45249632 7.86842105,6.49969895 C7.86842105,5.54690158 8.62614555,4.78917263 9.57894737,4.78917263 Z" id="Shape"></path>
</g>
</g>
</g>
0
</div>
<div class="brick__image-inner "
id="js-image-2799"
v-lazy-bg="'https://madewithnetworkfra.fra1.digitaloceanspaces.com/spatie-space-production/28323/harlem-2.jpg'"
style=" ">
</div>
</div>
<div class="brick__caption">
<div class="brick__caption-upper">
<a href="https://madewithvuejs.com/harlem" class="brick__title">
Harlem
</a>
<span class="brick__tagline">
Global State Management for Vue 3
</span>
</div>
<div class="brick__caption-lower">
<div class="pull-left">
<a href="/utilities">#Utilities</a>
<a href="/state-management">#State management</a>
</div>
<div class="pull-right">
<div class="brick__views">
<svg class="svg-inline u__va--middle" width="20px" height="13px" viewBox="0 0 20 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>icon-eye-dark</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Icons" transform="translate(-313.000000, -230.000000)" fill-rule="nonzero" fill="#435B71">
<g id="icon-eye-dark" transform="translate(313.000000, 230.000000)">
<path d="M9.57827924,0.0932647368 C5.40166592,0.0977121053 2.23909537,3.28527789 0.162366374,6.09013053 C-0.0164829778,6.33193046 -0.0164829778,6.66216481 0.162366374,6.90396474 C2.23923803,9.71401737 5.40085034,12.9011726 9.57827924,12.9068174 C9.57872397,12.9068178 9.57916871,12.9068178 9.57961345,12.9068174 C13.7562192,12.90237 16.9187977,9.71477 18.9955264,6.90995158 C19.1743758,6.66815165 19.1743758,6.3379173 18.9955264,6.09611737 C16.9186536,3.28603053 13.7570499,0.0989094737 9.57961345,0.0932647368 C9.57916871,0.0932643033 9.57872397,0.0932643033 9.57827924,0.0932647368 L9.57827924,0.0932647368 Z M9.57827924,1.46168579 L9.57961345,1.46168579 C12.8675916,1.46681737 15.5988249,3.96846211 17.5569468,6.50236737 C15.5985806,9.03230421 12.8660264,11.5341884 9.57961345,11.5383963 C6.29104758,11.5339489 3.55933568,9.03192789 1.60094367,6.49771474 C3.55930832,3.96777789 6.29185563,1.46589368 9.57827718,1.46168579 L9.57827924,1.46168579 Z" id="Shape"></path>
<path d="M9.57894737,3.42075158 C7.88659608,3.42075158 6.5,4.80737263 6.5,6.49969895 C6.5,8.19205947 7.88659608,9.57864632 9.57894737,9.57864632 C11.2712987,9.57864632 12.6578947,8.19205947 12.6578947,6.49969895 C12.6578947,4.80737263 11.2712987,3.42075158 9.57894737,3.42075158 Z M9.57894737,4.78917263 C10.5317492,4.78917263 11.2894737,5.54690158 11.2894737,6.49969895 C11.2894737,7.45249632 10.5317492,8.21022526 9.57894737,8.21022526 C8.62614555,8.21022526 7.86842105,7.45249632 7.86842105,6.49969895 C7.86842105,5.54690158 8.62614555,4.78917263 9.57894737,4.78917263 Z" id="Shape"></path>
</g>
</g>
</g>
0
</div>