Pinia: $reset alternative when using setup syntax - vue.js

I have a pinia store created with setup syntax like:
defineStore('id', () => {
const counter = ref(0)
return { counter }
})
Everything has been working great with setup syntax because I can re-use other pinia stores.
Now, however, I see the need to re-use Pinia stores on other pages but their state needs to be reset.
In Vuex for example, I was using registerModule and unregisterModule to achieve having a fresh store.
So the question is: How to reset the pinia store with setup syntax?
Note: The $reset() method is only implemented for stores defined with the object syntax, so that is not an option.
Note 2: I know that I can do it manually by creating a function where you set all the state values to their initial ones
Note 3: I found $dispose but it doesn't work. If $dispose is the answer, then how it works resetting the store between 2 components?

You can use a Pinia plugin that adds a $reset() function to all stores:
On the Pinia instance, call use() with a function that receives a store property. This function is a Pinia plugin.
Deep-copy store.$state as the initial state. A utility like lodash.clonedeep is recommended for state that includes nested properties or complex data types, such as Set.
Define store.$reset() as a function that calls store.$patch() with a deep-clone of the initial state from above. It's important to deep-clone the state again in order to remove references to the copy itself.
// store.js
import { createPinia } from 'pinia'
import cloneDeep from 'lodash.clonedeep'
const store = createPinia()
1️⃣
store.use(({ store }) => {
2️⃣
const initialState = cloneDeep(store.$state)
3️⃣
store.$reset = () => store.$patch(cloneDeep(initialState))
})
demo
Feel free to read the article based on this answer: How to reset stores created with function/setup syntax

You can do this as suggested in the documentation here
myStore.$dispose()
const pinia = usePinia()
delete pinia.state.value[myStore.$id]

Related

Vue computed() not triggered on reactive map

I have a reactive around a map that's initially empty: const map = reactive({});, and a computed that tells if the map has a key "key": const mapContainsKeyComputed = computed(() => map.hasOwnProperty("key")). The computed doesn't get updated when I change the map.
I stuck with this issue for a day and managed to come up with a minimum example that demonstrates the issue:
<script setup>
import {computed, reactive, ref, watch} from "vue";
const map = reactive({});
const key = "key";
const mapContainsKeyComputed = computed(() => map.hasOwnProperty(key))
const mapContainsKeyWatched = ref(map.hasOwnProperty(key));
watch(map, () => mapContainsKeyWatched.value = map.hasOwnProperty(key))
</script>
<template>
Map: {{map}}
<br/>
Computed: does map contain "key"? {{mapContainsKeyComputed}}
<br/>
Watch: does map contain key? {{mapContainsKeyWatched}}
<br/>
<button #click="map[key] = 'value'">add key-value</button>
</template>
I've read a bunch of stackoverflow answers and the Vue docs, but I still can't figure it out.
why mapContainsKeyComputed doesn't get updated?
if the reactive doesn't "track" adding or removing keys to the map, why the Map: {{map}} (line 14) updates perfectly fine?
when I replace the map{} with an array[] and "hasOwnProperty" with "includes()", it works fine. How's that different?
how do I overcome this issue without the ugly "watch" solution where the "map.hasOwnProperty(key)" has to be duplicated?
EDIT: as mentioned by #estus-flask, this was a VueJS bug fixed in 3.2.46.
Vue reactivity needs to explicitly support reactive object methods. hasOwnProperty is rather low-level so it hasn't been supported for some time. Without the support, map.hasOwnProperty(key) tries to access key on non-reactive raw object and doesn't trigger the reactivity, so the first computed call doesn't set a listener that could be triggered with the next map change.
One way this could be fixed is to either define key initially (as suggested in another answer), this is the legacy way to make reactivity work in both Vue 2 and 3:
const map = reactive({ key: undefined })
Another way is to access missing key property on reactive object:
const mapContainsKeyComputed = computed(() => map[key] !== undefined)
Yet another way is to use in operator. Since Vue 3 uses Proxy for reactivity, that a property is accessed can be detected by has trap:
const mapContainsKeyComputed = computed(() => key in map)
The support for hasOwnProperty has been recently added in 3.2.46, so the code from the question is supposed to be workable with the latest Vue version.
map is not really a map. This would be different in any Vue 3 version if Map were used, it's supported by Vue and it's expected that map.has(key) would trigger reactivity.

migrating from global mixins in vue2 to global composables in vue3

I"m porting my new app from vue2 to vue3. Since mixins are not recommended way of reusing code in vue3, Im trying to convert them into composable.
I've 2 mixins (methods) __(key) and __n(key, number) in mixins/translate.js which will translate any word into the app's locale.
module.exports = {
methods: {
/**
* Translate the given key.
*/
__(key, replace = {}) {
// logic
return translation
}
Now this is how I converted it as
Composables/translate.js
export function __(key, replace = {}) {
// logic
return translation
}
and since I need these functions to be accessbile in every component without explicitly importing. I'm importing it in app.js
import {__, __n} from '#/Composables/translate.js';
Questions
__() is not accessible in every component. How to make this function accessible in every component without explicit import
Is this the right of doing things?
These functions are required essentially in every component, declaring them in every component is impractical.
#1, You can put it into the globalProperties object
import {__, __n} from '#/Composables/translate.js';
const app = createApp(AppComponent);
app.config.globalProperties.__ = __;
app.config.globalProperties.__n = __n;
#2, though opinion based, importing for every component that needs it would be my preferred way.

Reuse components with different Vuex stores in NuxtJS - Create dynamic/multiple VueX store instances

I have a vue.js/nuxt.js component in my UI that displays news based on a backend which can be queried with selectors (e.g. news-type1, news-type2).
I want to add a second instance of that component which uses exactly the same backend, but allows the user to use a few different selectors (e.g. news-type3, news-type4). The UI kinda works dashboard-like. Implementing that distinction in the .vue component file is no problem (just accept some props and display stuff conditionally to the user), but:
How do I reuse the vuex store? The code of the store for the new card stays exactly the same since the same backend is used. But I can't use the same instance of the store because the selectors and the loaded news should be stored per component and should not be shared between them. Surprisingly I haven't been able to find any easy solutions for that in nuxt. I thought this would be a common use case.
MWE for my use case:
/** Vuex store in store/news.js **/
export const state = () => ({
// per default only news-type1 is selected, but not news-type2. the user can change that in the UI
currentSelectors: ['news-type1'],
news = [] // object array containing the fetched news
});
export const mutations = {
// some very simple mutations for the stae
setSelectors (state, data) {
state.currentSelectors = data;
},
setNews (state, data) {
state.news = data;
}
}
export const actions = {
// simplified get and commit function based on the currentSelectors
async loadNews ({ commit, state }) {
const news = await this.$axios.$get(`/api/news/${state.currentSelectors.join(',')}`);
commit('setNews', news);
// ... truncated error handling
},
// Helper action. In comparison to the mutation with the same name, it also calls the load action
async setSelectors ({ commit, dispatch }, selectors) {
commit('setSelectors', selectors);
dispatch('loadNews');
},
};
In my news-card.vue I simply map the two states and call the two actions loadNews (initial load) and setSelectors (after user changes what news to show in the UI). This should stay the same in both instances of the card, it just should go to different store instances.
My current alternative would be to simply copy-paste the store code to a new file store/news-two.js and then using either that store or the original store depending on which prop is passed to my news-card component. For obvious reasons, that would be bad practice. Is there a better complicated alternative that works with nuxt?
All related questions I have found are only for Vue, not for nuxt vuex stores: Need multiple instances of Vuex module for multiple Vue instances or How to re-use component that should use unique vuex store instance.

Vue composition API store

I was wondering if it was possible with the new Vue composition API to store all the ref() in one big object and use that instead of a vuex store. It would certainly take away the need for mutations, actions, ... and probably be faster too.
So in short, is it possible to have one place for storing reactive properties that will share the same state between different components?
I know it's possible to have reusable code or functions that can be shared between different components. But they always instantiate a new object I believe. It would be great if they would depend on one single source of truth for a specific object. Maybe I'm mixing things up...
You can use reactive instead of ref.
For eg.
You can use
export const rctStore = reactive({
loading: false,
list: [],
messages: []
})
Instead of
export const loading = ref(false)
export const list = ref([])
export const messages = ref([])
Cheers!

How to share variable with Nuxt.js from layout to pages?

I actually work on a project that consists of display data from Jsons on a Nuxt.js website using Vuetify. I have created a selector in my layout to choose which Json the user wants to display. I need to access this variable from all the different pages of my project.
Here is what my default.vue looks like :
<template>
<v-overflow-btn
:items="json_list"
label="Select an Json to display"
v-model="selected_json"
editable
mandatory
item-value="text"
></v-overflow-btn>
</template>
<script>
export default {
data() {
return {
selected_json: undefined,
json_list: [
{text: "first.json"},
{text: "second.json"},
],
}
}
}
</script>
The variable I would like to access from all my different pages is selected_json.
I see many things on the internet such as Vuex or a solution that consist to pass the variable with the URL. But I'm kind of newby in web programming (started Vue/Nuxt one week ago) and I don't really understand how to apply this in my project. So if there is a more easy way to do it or a good explaination, I'm interested!
Thanks in advance for your help :)
Using Vuex we can easily achieve what you want.
First of all create a file index.js in folder store (if you don't have a store folder then create it in the same directory where your pages, plugins, layouts etc folders are). Then paste this piece of code in index.js
//store/index.js
export const state = () => ({
selected_json: null
})
By doing this we set Vuex up. More precisely just state part of Vuex where if you don't know we store data accessible across your project.
Now we have to assign data from your default.vue to Vuex. We can achieve this by creating a mutation function through which we change state in Vuex. Add this to your index.js
//store/index.js
export const mutations = {
setSelectedJson(state, selectedJson) {
state.selected_json = selectedJson
}
}
Here function setSelectedJson takes two params, state which is automatically passed in by Nuxt.js and it includes all our Vuex state data. The second parameter selected_json we pass in ourselves.
Now in your default.vue we need to add a watcher for selected_json so we can update our Vuex when selected_json gets updated.
//layouts/default.vue
export default {
watch: {
selected_json(newValue) {
this.$store.commit("setSelectedJson", newValue)
}
}
}
We are almost done.
The last thing we need to do is to make a getter which is used to retrieve values from Vuex. A getter like this will do its job.
//store/index.js
export const getters = {
getSelectedJson(state) {
return state.selected_json
}
}
That's it.
Now you can access selected_json on any page you want by simply getting it from Vuex with this line of code.
this.$store.getters["getSelectedJson"]