The days of Vuex as Vue’s default state-management tool are long gone and Pinia has become the new standard. It provides well built functionality for global state-management. But do you even need a dedicated state-management library? And if not, how do you decide?
Vue's Composition API as state-management alternative
With Vue 3's Composition API there is a built-in alternative for state-management By using the built in functions you can organize your code and share data between components and component trees only where you need it. This approach provides some clear benefits:
- Only provide state where needed
- No additional setup required
- No additional dependency needed
- Keep different parts of your software separated.
What is a "Composable"?
Composables make use of the provided Composition API methods (refs, reactive, computed, lifecycle hooks, etc.). See https://vuejs.org/guide/reusability/composables for in-depth information.
Example
By knowing the following patterns you can build the same systems as with a traditional state-management system with composables.
In this example you can have multiple user stores that can have their state and load users with the help of the Composition API. You can even use the Vue Lifecycle Hooks to automatically fetch the users in the users view. When providing attributes to the use users function you can customize the different user stores e.g. by using feature flags.
Example useUsers.ts
helper:
import { ref, onMounted } from 'vue'
import type { Ref } from 'vue'
import { usersRepository } from '../api'
import type { User } from '../types'
export interface UsersHelper {
users: Ref<User[]>
loadUsers(): Promise<void>
}
export default (): UsersHelper => {
// State
const users = ref<User[]>([])
// Actions e.g. load from remote resource
const loadUsers = async (): Promise<void> => {
users.value = await usersRepository.load()
}
// Trigger load when helper is called in component (optional)
onMounted(() => {
void loadUsers()
})
return { users, loadUsers }
}
Use inject / provide instead of a global store
In conjunction with using composables as the main place for your state you should use the built in inject & provide functions to pass the state from parent to child components (where needed) in the same component tree.
Example
Setup injection key (injection-keys.ts
)
import type { InjectionKey } from 'vue'
import type { UsersHelper } from './useUsers'
export const usersHelperKey: InjectionKey<UsersHelper> = Symbol('usersHelper')
Provide in parent component
import { provide } from 'vue'
import useUsers from './useUsers'
import { usersHelperKey } from './injection-keys'
const usersHelper = useUsers({ autoLoad: true })
provide(usersHelperKey, usersHelper)
Inject in child component
import { inject } from 'vue'
import { usersHelperKey } from './injection-keys'
const usersHelper = inject(usersHelperKey)
Why you'd still want to use Pinia
Easy Global Data Handling:
Pinia is registered globally. That means that you can always access the stores throughout your app. Which makes data handling very convenient.Effective Debugging with Vue DevTools:
Pinia integrates well with Vue DevTools, offering detailed support. It provides clearer insights into state changes, making debugging and troubleshooting smoother.Patterns & predictability
For large-scale apps with many independent modules and teams, Pinia's clear structure can increase predictability.
Verdict
Vue 3’s Composition API gives you a powerful, dependency-free way to organize and share state. Pinia offers clear patterns for organizing and accessing global state. For new projects, consider using only composables with provide/inject instead of defaulting to a separate state-management library.
Top comments (0)