Passing Reactive State from Vuex to Composable - javascript

I'm working on a composable that's meant to encapsulate some logic with Vue3 and Vuex. The composable is working with a "feed" that is liable to change in the future, and comes from Vuex.
I'd like the composable to return the status of that feed when the feed changes as a computed value.
However, I'm unclear on how to fetch/wrap the value from Vuex so this computed property will change when the value in Vuex changes. For instance, at the top of the composable, I'm passing in the ID of the feed, fetching it from Vuex, and then using it in the composable like this:
const feed = store.getters['feeds/getFeedById'](feedId)
I'm then using the feed in a computed, inside of the composable, like this:
const feedIsReady = computed(() => feed.info.ready ? 'READY' : 'NOT READY')
However, when I change the feed object in Vuex via a mutation elsewhere in the application, the feed inside the composable does not change.
I've tried wrapping the feed in a reactive call and it's individual properties with toRefs but those approaches only provide reactivity within the composable itself, and don't capture changes from Vuex.
How would one wrap the feed from Vuex to provide reactivity? I need the changes in Vuex to propagate to my composable somehow.

Did you try to use vuex getter in your composable with computed property:
const feed = computed(() => store.getters['feeds/getFeedById'](feedId));

I see you are using store.getters['feeds/getFeedById'](feedId), which, AFAICT, means that the getter store.getters['feeds/getFeedById'] returns a function, and that the feedId is a parameter passed to the returned function.
If this is the case, this probably won't work, because that function likely doesn't return a reactive value.
I can't see the vuex code so don't know for sure, but assuming this is the case I would do something like this
const store = Vuex.createStore({
state() {
return {
feeds: {
1334:{info:{ready:false}},
}
}
},
getters: {
feeds(state) {
return state.feeds
}
},
mutations: {
change(state) {
state.feeds[1334].info.ready = !state.feeds[1334].info.ready;
}
}
});
function watchFeedState(feedid) {
const feeds = Vue.computed(() => store.getters.feeds)
const isReady = (v) => v && v.info && v.info.ready ? 'READY' : 'NOT READY';
const feedReady = Vue.ref(isReady(feeds[feedid]));
Vue.watch(store.getters.feeds, (v) => {
feedReady.value = isReady(v[feedid])
});
return feedReady
}
const app = Vue.createApp({
setup() {
const store = Vuex.useStore();
const isReady = watchFeedState(1334); // <= the feed from a higher order function
return {
isReady,
change: ()=>{store.commit('change')}
}
}
});
app.use(store);
app.mount("#app");
<script src="https://unpkg.com/vue#3.2.37/dist/vue.global.prod.js"></script>
<script src="https://unpkg.com/vuex#4.0.0/dist/vuex.global.js"></script>
<div id="app">
<button #click="change">toggle state</button>
{{ isReady }}
</div>
This creates a higher order function to watch a specific feed id. This will watch the feeds getter for every change, but only update for a provided feedid.

Related

Attempted to assign to readonly property

first of all i get my redux array then in my_function copy that into new variable like below :
let transactions_list = useSelector(state => state.transactions_list.value);
let new_transactions_list = [...transactions_list];
when i want to change my new_transactions_list very deeply i got the error
const my_function = () => {
let new_transactions_list = [...transactions_list];
new_transactions_list[yearIndex].data_yearly[monthIndex].data_monthly.push(new_obj);
}
but when i define an array in class(without redux), it's work
Even if you are using the spreading [...transactions_list], you are still only copying the first level of the array, which means that the object below that array is still the same one that redux uses.
You have 2 options:
This is how redux recommends you to update nested object link
function updateVeryNestedField(state, action) {
return {
...state,
first: {
...state.first,
second: {
...state.first.second,
[action.someId]: {
...state.first.second[action.someId],
fourth: action.someValue
}
}
}
}
}
Or you can use something like immer, which will allow you to update your object even with immutable like this
const nextState = produce(baseState, draft => {
draft[1].done = true
draft.push({title: "Tweet about it"})
})
Either way, you will have to update your redux state afterward since this change will only be local in your code and not the global redux.

Trying to get a value from a custom store Svelte

i want to ask something, i have a custom store like
const BlaBla = (Data) => {
const { subscribe, set, update } = writable(Data);
return {
subscribe,
update,
set,
setData: (NewData) => {
set(NewData)
},
getData: () => {
return <<<<<<< "Here lies the problem, how i can get the "newData"?."
}
}
}
i will explaying the scenario, im creating a script for a fivem server and im using svelte, i create a store that get a Vehicle with some properties like Name, Last Name, Plate and bla bla, i create the setData(Vehicle) and pass a set(Vehicle) then in another method i want to "get" the plate only, one solution i did was creating a variable in the scope and instead of a set i did an update like this
const VehicleStore = (Vehicle) => {
let Data = {} //Variable inside the scope
const { subscribe, set, update } = writable(Vehicle);
return {
subscribe,
update,
set,
setData: (NewData) => {
update((s) => {
s = NewData
Data = s
return s
})
},
getData: () => {
return Data.Plate
}
}
}
i don't know if this is the actual solution, i think im missing something
Svelte exports a get function that can be used to resolve the value of a store once (it is syntactic sugar around subscribe).
So first you have to get the value of the store, then you can access its property:
import { get } from 'svelte/store';
// ...
const store = writable(Data);
const { subscribe, set, update } = store;
// ...
return get(store).Plate
Note that accessing data like this will not be reactive because there is no persistent subscription to the store. You are generally not meant to use stores like that.
Instead you usually would use the store in a component's markup using auto subscriptions via $:
$VehicleStore.Plate

Is it ok to modify Vuex state using only the payload argument of a mutation?

For example, could I iterate over Vuex data in a Vue file and choose the data needing updating, then pass the found data to an action, which commits it and then the mutation only makes the update?
The reason I'm unsure about it is because the typical format of a Vuex mutation contains the parameter for 'state', so I assume it needs to be used, and the only way to do that is either by doing all the looping inside the mutation, or to pass indexes to it to more quickly find the exact fields needing changing.
For who asked, a code example:
someVueFile.vue
computed: {
...mapState({
arrayOfObjects: (state) => state.someVuexStore.arrayOfObjects
}),
},
methods: {
myUpdateMethod() {
let toBePassedForUpdate = null;
let newFieldState = "oneValue";
this.arrayOfObjects.forEach((myObject) => {
if (myObject.someDataField !== "oneValue") {
toBePassedForUpdate = myObject.someDataField;
}
})
if (toBePassedForUpdate) {
let passObject = {
updateThis: toBePassedForUpdate,
newFieldState: newFieldState
}
this.$store.dispatch("updateMyObjectField", passObject)
}
}
}
someVuexStore.js
const state = {
arrayOfObjects: [],
/* contains some object such as:
myCoolObject: {
someDataField: "otherValue"
}
*/
}
const mutations = {
updateMyObjectField(state, data) {
data.updateThis = data.newFieldState;
}
}
const actions = {
updateMyObjectField(state, data) {
state.commit("updateMyObjectField", data);
}
}
Yes, it's alright to mutate state passed in through the payload argument rather than state. Vuex doesn't bother to distinguish between the two. In either case, it's the same state, and neither option detracts from the purposes of using mutations.
To feel more sure of that, you can ask what are the purposes of mutations and of enforcing their use. The answer is to keep a centralized, trackable location for concretely defined changes to state.
To illustrate this is a good thing, imagine an app with 1000 components, each one changing state locally, outside of a mutation, and in different ways. This could be a nightmare to debug or comprehend as a 3rd party, because you don't know how or where state changes.
So mutations enforce how and a centralized where. Neither of these are damaged by only using the payload argument in a mutation.
I would do all of the logic from one action, you can desctructured the context object in the action signature like so :
actions: {
myAction ({ state, commit, getters, dispacth } ,anyOtherParameter) {
let myVar = getters.myGetter//use a getter to get your data
//execute logic
commit('myCommit', myVar)//commit the change
}
}
If you need to do the logic in your component you can easily extract the getter and the logic from the action.

want to show updated status value in another component

i want to watch when a mutation called and updated a status. i make a component to show database table count when api called.
this is my store i wrote
const state = {
opportunity: ""
}
const getters = {
countOpportunity: state => state.opportunity
}
const actions = {
// count opportunity
async totalOpportunity({ commit }) {
const response = await axios.get(count_opportunity)
commit("setOpportunity", response.data)
},
}
const mutations = {
setOpportunity: (state, value) => (state.opportunity = value)
}
i want to show this getter value when this mutation called in another component name Opportunity.vue file.
i showed database count values in file name Dashboard.vue
i wrote it like this.
computed: {
...mapGetters(["countOpportunity"])
},
watch: {},
mounted() {
//do something after mounting vue instance
this.$store.watch(() => {
this.$store.getters.countOpportunity;
});
},
created() {
this.totalOpportunity();
},
methods: {
...mapActions(["totalOpportunity"])
}
and showed my view like this.
<div class="inner">
<h3>{{ countOpportunity }}</h3>
<p>Opportunities</p>
</div>
when api called and count increase shows my mutations. but my view value not updated (countOpportunity). any one can help me to fix this.
The issue here (most likely) is that the value of response.data is an object or an array. You've initially defined opportunity as '' which is not an observable object or array. You have 2 choices:
Redefine it as an empty object or array, depending on the response:
opportunity: [] // or {}
Otherwise, use Vue.set() to apply reactivity when changing it:
(Vue.set(state, 'opportunity', value))

Can I do dispatch from getters in Vuex

Fiddle : here
I am creating a webapp with Vue 2 with Vuex. I have a store, where I want to fetch state data from a getter, What I want is if getter finds out data is not yet populated, it calls dispatch and fetches the data.
Following is my Vuex store:
const state = {
pets: []
};
const mutations = {
SET_PETS (state, response) {
state.pets = response;
}
};
const actions = {
FETCH_PETS: (state) => {
setTimeout(function() {
state.commit('SET_PETS', ['t7m12qbvb/apple_9', '6pat9znxz/1448127928_kiwi'])
}, 1000)
}
}
const getters = {
pets(state){
if(!state.pets.length){
state.dispatch("FETCH_PETS")
}
return state.pets
}
}
const store = new Vuex.Store({
state,
mutations,
actions,
getters
});
But I am getting following error:
Uncaught TypeError: state.dispatch is not a function(…)
I know I can do this, from beforeMount of Vue component, but I have multiple components which uses same Vuex store, so I have to do it in one of the components, which one should that be and how will it impact other components.
Getters can not call dispatch as they are passed the state not context of the store
Actions can call state, dispatch, commit as they are passed the context.
Getters are used to manage a 'derived state'.
If you instead set up the pets state on the components that require it then you would just call FETCH_PETS from the root of your app and remove the need for the getter
I know this is an older post and I'm not sure if this is good practice, but I did the following to dispatch from a getter in my store module:
import store from "../index"
And used the store inside my getter like this:
store.dispatch("moduleName/actionName")
I did this to make sure data was made available if it was not already present.
*edit:
I want you to be aware of this: Vue form - getters and side effects
This is related to #storsoc note.
If you need to dispatch from your getter you probably are already implementing your state wrong. Maybe a component higher up should already have fetched the data before (state lifting). Also please be aware that getters should only be used when you need to derive other data from the current state before serving it to your template otherwise you could call state directly: this.$store.state.variable to use in methods/computed properties.
Also thing about your lifecycle methods.. you could for example in your mounted or created methods check if state is set and otherwise dispatch from there. If your getter / "direct state" is inside a computed property it should be able to detect changes.
had the same Problem.. also wanted all Vue-Instances to automaticly load something, and wrote a mixin:
store.registerModule('session', {
namespaced: true,
state: {
session: {hasPermission:{}},
sessionLoaded:false
},
mutations: {
changeSession: function (state, value)
{
state.session = value;
},
changeSessionLoaded: function (state)
{
state.sessionLoaded = true;
}
},
actions: {
loadSession(context)
{
// your Ajax-request, that will set context.state.session=something
}
}
});
Vue.mixin({
computed: {
$session: function () { return this.$store.state.session.session; },
},
mounted:function()
{
if(this.$parent==undefined && !this.$store.state.session.sessionLoaded)
{
this.$store.dispatch("session/loadSession");
this.$store.commit("changeSessionLoaded");
}
},
});
because it loads only one per vue-instance and store and it it inlcuded automaticly in every vue-instance, there is no need to define it in every main-app
I use a getter to configure a dynamic page. Essentially, something like this:
getter: {
configuration: function () {
return {
fields: [
{
component: 'PlainText',
props: {},
setPropsFromPageState: function (props, pageState, store) {
// custom logic
}
}
]
};
}
}
Then in the page component, when I am dynamically setting the props on a dynamic component, I can call the setPropsFromPageState(field.props, this.details, this.$store) method for that component, allowing logic to be set at the config level to modify the value of the props being passed in, or to commit/dispatch if needed.
Basically this is just a callback function stored in the getter that is executed in the component context with access to the $store via it.

Categories

Resources