We all know Vuex. It is a great addition to the Vue ecosystem from the very beginning. It helped to manage the state in dozens of projects. It is very versatile and extensive. However, for small applications, it might be too big and complex, and the general flow too tangled and complicated. Back in the day, there were no good alternatives tho. So we used to it, every time when there was a need to share some data across the application we were using Vuex.
With Vue 3 and the new Composition API, our eyes shone. Because of its awesome reactivity system for many of us, it was obvious that now, we can share small states from composables in regular components. We started to doubt that maybe Vuex is not needed anymore. Despite this Vuex in version 4 was adopted to the new APIs and now with Vue 3 you can still use it and enjoy the old good Vue state management.
However, many developers decided to go with the new path and use composables to share small and reactive states in their projects. Turns out that rather this approach is quite convenient and comfortable it might cause - because of globally exposed data objects - some security issues, and memory leaks with/on SSR as well.
Because of these and willingness of creating something less complicated as Vuex, without mutations, commits, and data dispatching the idea of building Pinia was born. So Pinia is a new, built-in mind of Vue 3 (Composition API) ecosystem state management. It provides a quite simple API that might be similar in reception to other state-based patterned solutions out there. Pinia is able to handle state management in your application painless, it delivers a versatile and straightforward flow of data traveling/sharing in your project. Just check it … and read further.
Although Pinia is great and easy it adds a new abstraction to your development process - new stores, methods, flows. And if you're building a small application it might be (still) too mature and too complex for it. It's also a custom solution that is handling various scenarios and that weights.
So because I'm a huge fan of the Composition API and getting rid of this Vuex complexity I was also unhappy to deal with this approach of leaving the composable-based small states concept. And on top of that idea of this library (composable) came up.
In short 😏 - Vue Composition API provides something called EffectScope which is able to record all the effects created during the current instance existence. For example, you'll find there computed properties. What's more important this Effect Scope can be shared across the application area. Then, accordingly, to the original RFC of this feature, we can attach any additional data to it.
And that's how and why the vue-use-state-effect library was created. With it, your composable in any shape that you want to share can be wrapped and joined. Used in the other components afterward. Finally, without any additional abstraction, you can use it to create sharable states/stores inside your application - handling them via composables with your own custom logic. Still, kept the native-like flow of the development. Awesome right? In the end, to avoid data stacking you have destroy utility that you can use along with it anytime you want. So composable that is using Effect Scope to create state - Vue Use State Effect. ✨
Now, let's check how it works with some real-life example.
First, you need to - of course - install it then we can create our first data-related composable with some state and the function that will update it.
/* composables/useSharedState.ts */
import { ref } from 'vue'
const sharedState = () => {
const state = ref({
test: '🚀 Initial state value.',
})
const updateState = () => {
state.value = {
test: '🌝 Updated state value.',
}
}
return {
state,
updateState,
}
}
OK, we can import the vue-use-state-effect
and use it with our newly created composable. Like that ... Please notice that this is the same file/component, I’m just repeating it (snippet) to show the next step of importing the composable.
/* composables/useSharedState.ts */
import { useStateEffect } from 'vue-use-state-effect'
/* you composable logic, up there */
export const useSharedState: any = useStateEffect(sharedState, {
name: 'sharedState',
debug: true,
destroy: false,
})
Fantastic. We’ve just created the shared composable that we can use along with our components. Let’s create one and check how we can use it.
<!-- Home Page | home.vue -->
<template>
<div>{{ test }}</div>
<button @click="updateState">update state</button>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useSharedState } from '@composables/useSharedState'
const {
sharedState: { state, updateState },
} = useSharedState()
const test = computed(() => state.value.test) // '🚀 Initial state value.',
</script>
What you can see here is that we’ve gotten the state/store data from the composable. The parent object key is defined on top of the name
that we provided within the composable establishing. We’re using computed property to create the reactive one to reflect it in the template. Additionally, we’ve passed the update method with the help which we can use along with the button to update the state from the UI. Now we can create a new page to see/use saved or updated state. Like that.
<!-- New Page | new.vue -->
<template>
<div>{{ test }}</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useSharedState } from '@composables/useSharedState'
const {
sharedState: { state },
} = useSharedState()
const test = ref(state.value.test) // '🌝 Updated state value.',
</script>
And we have it. That’s it. You can now go and use your shared state (composable) across your application. In the end, if you would like to clear this data, to not stock it much inside the application lifecycle you can use destroy
option to handle it. Quick tip here tho — because of asynchronously rendered components (especially in Nuxt), if you need it, you can retrieve the reconstructed state with onMounted
hook. Like this.
<!-- New Page | new.vue -->
<script setup lang="ts">
import { onMounted } from 'vue'
const test = ref('')
onMounted(() => {
const {
sharedState: { state },
} = useSharedState()
test.value = state.value.test
})
</script>
Simple as it can be. Native as it can be. Nothing more. Not so sophisticated, not so complicated, but for the majority of the small Vue apps it might be the best, fastest, and most convenient solution. Just give it a try, now or with your next project.
Drawbacks? Yeah. It’s simple so you will not get the structural shape and flow like with Pinia or even Vuex. You’ll not check it within the devtools as well, but you have debug mode which might be enough replacement (I hope). Probably you’ll find more but it’s not for everybody and not for each project. It’s the meter of defining a balance. 😋
You can download it from the npm registry. You can find its repository on GitHub. And with the StackBlitz Nuxt 3 demo you can try it out in action, without even installing it. Want to help or contribute, please create some new GitHub Issue for it. Thanks for the support in advance.
Wanna support? Buy me a coffee or sponsor me via GitHub.
Cheers and Enjoy.