Using mapWritableState() with v-model for nested properties - vue.js

In an experimental Todo list app using pinia, I have a store State with a focusTaskId and taskById containing any locally-cached items, stored by their id.
How do I use v-model against one of the cached Tasks, given that it is not stored in a top-level key of the store?
interface State {
focusTaskId: string | null;
taskById: {
[k in string]?: Task;
};
}
export const useStore = defineStore("vault", {
state: (): State => ({
focusTaskId: null,
taskById: {},
}),
});
When an item is in focus, I want to bind the form to the corresponding Task stored under taskById within the store, using v-model. To do this, it must be a locally aliased writable property in the form component.
Unfortunately, to alias writable store properties, mapWritableState only accepts top-level keys from the store, such as ["focusTaskId"] or ["taskById"], and can't accept a general path expression e.g. [taskById['${id}']]
Is there a workaround so that I can dynamically bind my form via v-model to whatever object under taskById is currently focused, even though it is 'two levels down'?
I am coming from React and with my lauf store, I could partition a store for the taskById object, and bind from there using 'id' as the (new) top-level key. Perhaps there is an equivalent to derive a store from a child object of another store in pinia, then I can bind that new store at the top level?

Related

mapstate to dynamic state objects of vuex store from the component

I am trying to build a watchlist (data streaming program) by vue3 with vuex. When a watchlist component subscribes for a symbol it should receive updates for that symbol from the store. When removing the subscription from the component that particular component should not receive that state change after that. We cannot hardcode symbol names in store to mapstate from component for each individually since there can be hundreds. if we take all the symbols as an attribute of a single object and map the state to it it will be a performance overhead since not all watchLists are referring to all the symbols.
So my question is there any way to inject a dynamically changing array to mapstate?
In component->
computed: {
...mapState([this should change dynamically]),
},
in Store->
state : {
these states also should be dynamic
},
or is there any workaround in vue to achive this?
I have found an alternative way by using getters. When we are using that we don't have to map state at all.
I am returning a function from getters with an argument since Vuex getters don't accept arguments.
symbol: (state) => (symbol) => {
return state.payload[symbol]
}
and from the component, I am watching for that getter.
this.$store.watch(
(state, getters) => getters.allSymbols(currency),
(newValue, oldValue) => {
this.symbolObjects[symbol] = newValue;
// do something
},
);
the watch is triggering every time the currency update. Also using the Vuex watcher gives the benefit of accessing both old and new values.

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.

Vuex mutations without referencing state directly?

I am learning Vuex and my understanding so far has been that mutations should be simple functions that update the state directly using something like state.property = value or state.object = {payload}, e.g.:
SET_USER_DATA (state, userData) {
state.user = userData
}
I am working through a course from Vue Mastery that contains mutations that look like the code below which does not reference the state object within the body of the mutation at all:
CREATE_TASK(state, { tasks, name }) {
tasks.push({ name, id: generateId(), description: "" });
},
UPDATE_TASK(state, { task, field, value }) {
Vue.set(task, field, value);
},
CREATE_TASK creates a new empty task and adds it to the tasks array using tasks.push(), but shouldn't a reference to the state object be required to update the state? E.g. state.tasks.push()? How does simply pushing an item onto a bare array commit the change to the state?
In the second example, they use Vue.set() to update the value of a specific field within a task (e.g. name, description), but again, there is no reference to the state object here.
The best I can figure is that they relying on Vue's native reactivity to automatically update the state when calling Vue.set() or Array.push(). But if that is the case, wouldn't any usage of Vue.set() or Array.push() inside of a component also immediately update the state (violating the rule that state changes should only be handled within a mutation?

Tracking a child state change in Vue.js

I have a component whose purpose is to display a list of items and let the user select one or more of the items.
This component is populated from a backend API and fed by a parent component with props.
However, since the data passed from the prop doesn't have the format I want, I need to transform it and provide a viewmodel with a computed property.
I'm able to render the list and handle selections by using v-on:click, but when I set selected=true the list is not updated to reflect the change in state of the child.
I assume this is because children property changes are not tracked by Vue.js and I probably need to use a watcher or something, but this doesn't seem right. It seems too cumbersome for a trivial operation so I must assume I'm missing something.
Here's the full repro: https://codesandbox.io/s/1q17yo446q
By clicking on Plan 1 or Plan 2 you will see it being selected in the console, but it won't reflect in the rendered list.
Any suggestions?
In your example, vm is a computed property.
If you want it to be reactive, you you have to declare it upfront, empty.
Read more here: reactivity in depth.
Here's your example working.
Alternatively, if your member is coming from parent component, through propsData (i.e.: :member="member"), you want to move the mapper from beforeMount in a watch on member. For example:
propsData: {
member: {
type: Object,
default: null
}
},
data: () => ({ vm: {}}),
watch: {
member: {
handler(m) {
if (!m) { this.vm = {}; } else {
this.vm = {
memberName: m.name,
subscriptions: m.subscriptions.map(s => ({ ...s }))
};
}
},
immediate: true
}
}

Dynamically creating a reactive array in the Vuex's state

My component would like to add a new reactive-array field to the SST (vuex). I tried in beforeCreate hook, but the added array is not reactive; it's just a plain JS array.
Note that this is not the same as adding/removing elements from an existing array created at the Vue's initialization time. Such arrays are "wrapped" and become reactive as expected, mindful of "Array Change Detection" gotchas.
In my case, I'm trying to dynamically add an entirely new field of array type to the SST and make it reactive at the same time. Possible?
Have a look at Reactivity in Depth - Change Detection Caveats:
Change Detection Caveats
Due to the limitations of modern JavaScript, Vue cannot detect property
addition or deletion.
Since Vue performs the getter/setter conversion process during
instance initialization, a property must be present in the data object
in order for Vue to convert it and make it reactive.
But you say you are adding an array dynamically:
I'm trying to dynamically add an entirely new field of array type to the SST and make it reactive at the same time. Possible?
From the docs (bold is mine):
Vue does not allow dynamically adding new root-level reactive properties to an already created instance. However, it’s possible to add reactive properties to a nested object using the Vue.set(object, key, value) method:
Vue.set(vm.someObject, 'myArrayName', [1,2,3]);
Which should help you making your array reactive.
I see two problems here:
add dynamically array using vuex.
add dynamically element to this array and render this element.
I've initiate array if not exist in add method because when I'm receiving data from server myArray is not exist.
My solutuion below:
myVuexArray.js
import Vue from 'vue'
const state = {
myObject: {
myArray: [],
}
}
const getters = {
getMyArray: state => {
return state.myObject.myArray;
}
}
const mutations = {
addElementToArray(state, value) {
if (state.myObject.myArray === null || state.myObject.myArray === undefined || state.myObject.myArray === '') {
// initiate array
state.myObject.myArray = [];
}
// add new element to array
Vue.set(
state.myObject.myArray,
state.myObject.myArray.length,
value
);
// creates a new array everytime this solves the reactivity issue
Vue.set(state, 'myObject.myArray', state.myObject.myArray);
return state.myObject.myArray;
},
removeElementFromArray(state, index) {
state.myObject.myArray.splice(index, 1);
}
}
export default {
state,
mutations,
getters
}
Best regards
Dynamic module registration could help you to achieve this :
https://vuex.vuejs.org/en/modules.html
This would allow you to dynamically register a new module containing your array field in the beforeCreate hook.