It's time to ditch the state managers!
For years the de facto state manager for Vue was Vuex and now with the release of Vue 3 it changed to Pinia. This idea of state managers is common to any front-end framework today. But do we really need them anymore? I mean to add a plugin to do this. Vue has ref
and reactive
now and SolidJS has signals
. Svelte just handles variables. We can use these in a module itself without adding anything and replace our Vuex/Pinia stores.
I was actually replacing Vuex to Pinia in one of my apps when I came to this conclusion that what is the point? I have to add the store as a module in every file where I use it and also import the functions that handle or the actions/mutations. Why wouldn't those just be functions in a javascript module with Vue reactive
object?
Here's a simple Pinia store:
import { defineStore } from 'pinia'
export const time = defineStore({
id: 'time',
state: () => ({
hours: 0,
minutes: 0,
seconds: 0,
}),
getters: {
inMilliseconds: (state) => {
return state.hours * 3600000
+ state.minutes * 60000
+ state.seconds * 1000;
}
},
actions: {
addHours() {
this.hours += 1;
},
addMinutes() {
this.minutes += 1;
if (this.minutes >= 60) {
this.hours += 1;
this.minutes = this.minutes % 60;
}
},
addSeconds() {
this.seconds += 5;
if (this.seconds >= 60) {
this.minutes += 1;
this.seconds = this.seconds % 60;
}
},
}
})
Now let's create a simple JS module with Vue reactive
object to replace the Pinia store above:
import { reactive, computed } from "vue";
export const time = reactive({
hours: 0,
minutes: 0,
seconds: 0,
inMilliseconds: computed(() => {
return time.hours * 3600000
+ time.minutes * 60000
+ time.seconds * 1000;
})
})
const addSeconds = () => {
time.seconds += 5;
if (time.seconds === 60) {
time.seconds = 0;
time.minutes += 1;
}
}
const addMinutes = () => {
time.minutes += 5;
if (time.minutes >= 60) {
time.hours += 1;
time.minutes = time.minutes % 60;
}
}
const addHours = () => {
time.hours += 1;
}
export default function useTime() {
return {
time,
addSeconds,
addMinutes,
addHours,
}
}
Above is basically the same store using just reactive
for state and computed
for getters. What remains is just javascript. This way the code can be much more easier to read and the handling the state can be simpler. Rememnber, no additional javascript needs to be loaded for this.
It's easy to write tests and handle side effects like mutating hours
when we have enough minutes
.
Here's how you could display the data:
<template>
<div class="flex-column">
<div class="flex-row">
<div>{{ time.hours }} hours</div>
<div>{{ time.minutes }} minutes</div>
<div>{{ time.seconds }} seconds</div>
</div>
<div class="flex-row">
<div>And that is {{ time.inMilliseconds }} milliseconds</div>
</div>
</div>
</template>
<script>
import useTime from "./store/time.js";
export default {
setup() {
const { time } = useTime()
// add the time "store" to the component
return { time }
},
}
</script>
And here's how a component would mutate the data by importing the proper function and bind a click event to it:
<template>
<div>
<button @click="addSeconds">Add seconds</button>
</div>
</template>
<script>
import useTime from "./store/time.js";
export default {
setup() {
const { addSeconds } = useTime()
return { addSeconds }
}
}
</script>
If you already have a big application built with Pinia, I'm not suggesting to get rid of it. This is a thought that I had converting from Vue 2 to 3 and had a Vuex store to convert to Pinia and thought that I could just use something else. If you have a Vue 2 app, you could upgrade to Vue 2.7 first, convert your Vuex stores to this form and then the conversion to Vue 3 will be much easier.