Since the release of Vue 3, the widely used Vuex library is not that popular anymore and has officially been replaced by Pinia as the "official state management library for Vue". Pinia can be used with Vue 2 and Vue 3 and started out as an experiment to explore what a Vue store library could look like with the new Composition API (it can now be used without the Composition API, too).
Indeed the Composition API allows for a lot of new possibilities when it comes to state management. Composables, optionally using the provide
and inject
API, even make a complex state management library completely obsolete for smaller applications. Instead of having a dedicated Vuex module for a part of the store, it is now enough to have a single composable keeping the state and logic nicely isolated and still being usable everywhere:
// useIssues.js
export const issues = ref([])
export async function fetchIssues () {
const results = (await fetch('/issues')).json()
issues.value = results
}
export async function createIssue (issue) {
await fetch('/issues/', { method: post, body: JSON.stringify(issue) })
issues.value.push(issue)
}
I am, however, a fan of keeping logic in dedicated container components. Consider the following example: we have an application where our users can create issues regarding our product (feature requests, bugs reports etc.). In our own backend application, we can then see and work on these issues. In this case, we could have a container component Issues
containing the logic and having multiple child components.
As you can see, all the state as well as the functions concerning this part of the application, are in the same component. To me it doesn't get any simpler than this. The Issues
container then communicates to the sub-components the Vue way: using Props and Events.
Let's now consider that we have another container component: Users
. In here we have everything concerning the users of our application. We should be able to see a list of all of them and we want to have functions to edit and delete them.
As you can see, the two components are more or less the same: listing items and showing a detail view, as well as offering some ways to edit the them. This is a great separation of concerns in my opinion: everything regarding the issues is in the Issues
component, everything about the users is in the Users
component.
Now, in reality, things are often not as simple as they should be. As soon as we finish developing this application, our product manager might come to us with a great idea: in the detail view of the user we would like to see a list of the issues created by this user. What sounds like a good and innocent idea now quickly becomes a nightmare: using our simple component model, there is now way that those two sibling components can talk to each other without using a state management tool like Pinia. However, using a state management tool just for this case seems like overkill. If those components just had a way of communicating with each other...
Introducing vue3-ref-registry
. When confronted with exactly this problem, i started to dig a bit deeper into it and explore different solutions. One cool feature of Vue are component refs, with which rendered components can be programmatically communicated with. Usually it's used in the context of a parent component talking to a child component but i figured it could also be used as a way of inter-component communication - sibling components talking to each other. Or, for that matter, any component talking to any other component.
The concept is really easy: the plugin must be installed for the Vue instance and from this point on, whenever you use a component ref anywhere in your application, it is automatically added to the central registry and can be addressed from anywhere. Here's how it would be used in our fictional example:
<!-- in App.vue -->
<template>
<issues ref="issuesComponent" />
<users ref="usersComponent" />
</template>
Those two component refs can now be addressed from anywhere, for example in the Users
component:
import { useRefRegistry } from 'vue3-ref-registry'
export default () {
const registry = useRefRegistry()
const userIssues = computed ((user) => () => {
return registry.value.issuesComponent.issues.filter(issue => issue.user === user.id)
})
return {
userIssues
}
}
This makes it possible to keep things simple: having all the state in dedicated components, no need for a complex state management tool. Would i recommend this for every application? Surely not. It is however an easy alternative to refactoring your whole application when things get a bit more intertwined.
If you are interested in the library then go check it out on Github and give it a star. Also, please let me know when things don't work as expected!