State management concept: state, view, and actions
State management consists of three parts: the state (source of truth driving the app), the view (declarative mapping of the state), and the actions (possible ways the state could change in reaction to user inputs from the view). This follows the one-way data flow pattern.
Problems with shared state across multiple components
When multiple components share a common state, two issues arise: multiple views may depend on the same piece of state, and actions from different views may need to mutate the same piece of state. Lifting shared state to a common ancestor and passing it down as props leads to prop drilling. Using template refs or emitting events to synchronize multiple copies of state are brittle patterns that lead to unmaintainable code.
Global singleton pattern for shared state
The simpler solution to shared state is extracting it from components and managing it in a global singleton. With this pattern, the entire component tree becomes a 'view', and any component can access the state or trigger actions regardless of where they are in the tree.
Creating shared reactive state with reactive()
Create a shared reactive object using the reactive() function and export it from a module. Multiple components can import this store and use it directly. When the store object is mutated, all components using it will automatically update their views, creating a single source of truth.
Simple state management example with reactive()
Example store.js file: import { reactive } from 'vue'; export const store = reactive({ count: 0 }). Then import and use in components like: import { store } from './store.js'; template: <template>{{ store.count }}</template>. For Options API, return store in data() method.
Centralize state mutations with store methods
To maintain centralized state-mutating logic like the state itself, define methods on the store object with names that express the intention of the actions. For example, add an increment() method to the store instead of allowing components to mutate store.count directly. Call store methods with parentheses (e.g., store.increment()) to ensure the proper 'this' context.
Store methods must use parentheses when called
When calling store methods as click handlers or in templates, always use parentheses (e.g., @click="store.increment()"). This is necessary to call the method with the proper 'this' context since the method is not a component method.
Sharing reactive state with ref() and computed()
In addition to reactive(), you can share reactive state created using other Reactivity APIs such as ref() or computed(), or return global state from a Composable. Vue's reactivity system is decoupled from the component model, making it extremely flexible for state management.
SSR considerations for global singletons
When building an application with Server-Side Rendering (SSR), using singleton stores can lead to issues because the store is shared across multiple requests, potentially causing cross-request state pollution. This requires special handling discussed in the SSR guide.
Pinia as recommended state management library
Pinia is the official state management library recommended for Vue applications, maintained by the Vue core team, and works with both Vue 2 and Vue 3. It provides stronger conventions for team collaboration, integration with Vue DevTools (including timeline, in-component inspection, and time-travel debugging), Hot Module Replacement, and SSR support. Compared to Vuex, Pinia provides a simpler API with less ceremony, Composition-API-style APIs, and solid type inference support with TypeScript.
Vuex is in maintenance mode
Vuex is the previous official state management library for Vue and is now in maintenance mode. It still works but will no longer receive new features. New applications should use Pinia instead.
Pinia history and relationship to Vuex 5
Pinia started as an exploration of what the next iteration of Vuex could look like, incorporating ideas from core team discussions for Vuex 5. Eventually, the team realized Pinia already implemented most of what was desired in Vuex 5, so they made Pinia the new official recommendation instead of continuing Vuex.