Vue 3 State Management Standards · VUE-04
Stores provide centralised, clear patterns for state management. To name one of the many benefits; by using stores for state, components can focus on presentation. Separating concerns in UI code promotes cleanliness and maintainability, which is crucial for creating large scale-applications.
Share State Across Components
Store Structure and Modules
Use Centralised State Management · VUE-04.1 · MUST
When a composable’s state must be used across multiple components, use the Composition API’s provide/inject, or Pinia for state management.
This allows state passing down a component tree without prop drilling. For complex state management, use Pinia.
Organise Stores in the src/stores Directory · VUE-04.2 · MUST
Name subdirectories according to the module or feature they represent (e.g. messages, auth, products).
Prefix Store Names with use · VUE-04.3 · MUST
As stores are composables, their names must always start with use.
Suffix Store Names with store · VUE-04.4 · SHOULD
For organisation and easy identifications store names should end with store. Therefore they get a pragmatic name of useChatStore, useShoppingBasketStore, useRegistryStore, etc.
Define Simple Stores in a Single File · VUE-04.5 · SHOULD
Simple stores should be defined in a single file.
Define Complex Stores in Multiple Files· VUE-04.6 · SHOULD
For complex stores, split the state, getters, actions and mutations into separate files within a modular store directory.
Simple State Management Patterns
Use Vue Reactive Singletons to ensure a single source of truth · VUE-04.7 · MUST
Manage shared reactive state across multiple components using a singleton pattern, ensuring a single source of truth.
A reactive singleton is a simple, lightweight solution for managing shared state for small features or projects. Pinia on the other hand, is better suited for larger applications, requiring advanced statement management features such as plugins, devtools integrations and store modules.
A reactive class is created on app load, any changes to this class persist globally, this allows you to have a single, reactive source of truth for logic that can be easily imported and used in various parts of a Vue application.
Components can interact with this helper, and any changes will be reactively reflected wherever it’s used.
Vue reactive Singleton Example
import {reactive} from 'vue';
/**
* A singleton store for managing a counter.
*
* @returns {Object} An object containing:
* - {Object} state - The reactive state object.
* - {Function} increment - A function to increment the counter.
* - {Function} reset - A function to reset the counter.
*
* @example
* import { counterStore } from '@/store/useCounterStore';
* const { state, increment, reset } = counterStore;
*/
const state = reactive({
count: 0,
});
function increment() {
state.count++;
}
function reset() {
state.count = 0;
}
export const counterStore = {
state,
increment,
reset,
};Best Practices for State Management
Ensure Shared State remains reactive · VUE-04.8 · MUST
Shared state must remain reactive by using either ref or reactive within composables.
Shared State Example
export function useCounter(initialValue = 0) {
// Define a reactive reference for the counter value
const count = ref(initialValue);
function increment() {
count.value++;
}
function reset() {
count.value = initialValue;
}
return {count, increment, reset};
}Do not introduce Global Side Effects · VUE-04.9 · MUST
Keep shared state management isolated, avoidind unintended side effects across components.
In the following example, the variable globalCounter introduces maintainability and scalability issues:
- No isolation leads to unintended side effects across any component which uses the composable.
- Changes to one component will affect all components using the composable.
- As the application grows, managing and tracking changes to the global state becomes cumbersome and error-prone.
Global Side Effect Example (❌)
import {ref} from 'vue';
// Bad practice
let globalCounter = ref(0);
export function useGlobalCounter() {
function increment() {
globalCounter.value++;
}
function reset() {
globalCounter.value = 0;
}
return {count: globalCounter, increment, reset};
}