I trying to have a state shared between some components (sub-components) that where each sub-combonent (and parent component) can update the shared property (store in vueX state).
I have make a small "How to reproduce" here:
Vue.component('urlQueryComponent', {
template: '<div>object: {{pathQuery}}</div>',
computed: {
pathQuery () {
return this.$store.state.urlQuery;
}
}
})
https://codepen.io/anon/pen/rvmLrZ?editors=1010
The problem it's when I update state in sub component, the changes are not handled.
The VueX instance:
const store = new Vuex.Store({
state: {
urlQuery: {
path: '',
query: {}
}
},
mutations: {
pushQuery: (state, type) => {
state.urlQuery.query[type.key] = type.value;
console.log('urlQuery: ', state.urlQuery);
},
pushPath: (state, path) => {
state.urlQuery.path = path;
}
},
getters: {
getUrlQuery: state => state.urlQuery
}
})
And the parent component:
new Vue({
el: '#app',
store,
methods: {
changeType (type) {
this.$store.commit('changeType', type);
}
}
})
EDIT:
After reflection, The code before are not realy targeted to my problem. This fiddle are more targeted to my problem.
Change your pushQuery mutation to:
pushQuery: ({ urlQuery }, type) => {
const key = type.key
Vue.set(urlQuery.query, key, type.value)
console.log('urlQuery: ', urlQuery);
}
it should work (fiddle here)
Source (Vuex mutations) :
Mutations Follow Vue's Reactivity Rules
Since a Vuex store's state is made reactive by Vue, when we mutate the state, Vue components observing the state will update automatically. This also means Vuex mutations are subject to the same reactivity caveats when working with plain Vue:
Prefer initializing your store's initial state with all desired fields upfront.
When adding new properties to an Object, you should either use : Vue.set(obj, 'newProp', 123)
Related
I use vuex for my state as well as fetching data and display it in my application.
But I wonder if I'm doing it right. At the moment I dispatch an fetchDataAsync action from the component mounted hook, and I have an getter to display my data. Below is a code example of how I do it currently.
I wonder if it's necessary. What I really want is a getter, that looks at the state, checks if the data is already there and if the data is not there it is able to dispatch an action to fetch the missing data.
The API of vuex does not allow it so I need to put more logic into my components. E.g. if the data is depended of a prop I need a watcher that looks at the prop and dispatches the fetchDataAsync action.
For me it just feels wrong and I wonder if there is a better way.
let store = new Vuex.Store({
state: {
posts: {}
},
mutations: {
addPost(state, post) {
Vue.set(state.posts, post.id, post);
}
},
actions: {
fetchPostAsync({ commit }, parameter) {
setTimeout(
() =>
commit("addPost", { id: parameter, message: "got loaded asynchronous" }),
1000
);
}
},
getters: {
// is it somehow possible to detect: ob boy, I don't have this id,
// I'd better dispatch an action trying to fetch it...?
getPostById: (state) => (id) => state.posts[id]
}
});
new Vue({
el: "#app",
store,
template : "<div>{{ postToDisplay ? postToDisplay.message : 'loading...' }} </div>",
data() {
return {
parameter: "a"
};
},
computed: {
...Vuex.mapGetters(["getPostById"]),
postToDisplay() {
return this.getPostById(this.parameter);
}
},
methods: {
...Vuex.mapActions(["fetchPostAsync"])
},
mounted() {
this.fetchPostAsync(this.parameter);
}
});
I also created a codepen
Personally I think the solution you suggested (adding a watcher that dispatches fetchPostAsync if the post is not found) is the best one. As another commenter stated, getters should not have side effects.
I’m creating a custom module that fetches data in the “afterRegistration” hook and saves the result into the store.
The state is updated (can see the update in VueDevTools), but the component has still the default state. What I’m doing wrong?
// afterRegistration
export function afterRegistration ({Vue, config, store, isServer}) {
store.dispatch(${KEY}/isVisible)
}
// component
export default {
name: 'Test',
components: { Fragment },
props: {
…
},
computed: {
isVisible: el => el.$store.getters['myModule/simpleTest']
}
}
You have lost the reactivity. Set the initial value of simpleTest in state object to whatever you like (from the context i see it's Boolean). There is no getter or setter for this field, if it's not in initial state.
Ok, I found out that I need to dispatch in an AsyncLoader
// afterRegistration
export function afterRegistration ({Vue, config, store, isServer}) {
AsyncDataLoader.push({
execute: ({ route, store, context }) => {
return new Promise ((resolve, reject) => {
store.dispatch(`${KEY}/getInlineTranslationConfig`)
.then(() => resolve(null))
})
}
})
Now it works!
I have a Vue.js store with an array and a mutation that sets it after is is reloaded via an API:
export default new Vuex.Store({
state: {
triggeredTests: [],
mutations: {
setTriggeredTest(state, data) {
state.triggeredTests = _
.chain(data)
.forEach((item) => {
item.dateFormatted = moment(item.date).format('DD MMMM YYYY');
item.explanationTest = testMapping.get(item.test);
})
.orderBy('date')
.groupBy('date')
.value();
},
},
});
Should I use some specific mutation method to assign the array here to make the bound components refresh correctly?
The triggeredTests property is already in the store (via state:) so Vue has added change listeners and state.triggeredTests = newArray triggers a change.
You only need Vue.set(state, 'triggeredTests', newArray) when a property was not known before.
However changes may not be visible inside a Component that only listens to changes to an item in the previous array.
Using mapState() and using the triggeredTests variable you'll make sure changes to the array are reflected in the component.
computed: mapState({
item: state => state.triggeredTests.find( ... )
})
If you are resetting the entire array you can use Vue.Set() and create a copy of the array. Below is a rough version of this:
export default new Vuex.Store({
state: {
triggeredTests: [],
},
mutations: {
MUTATE_ITEMS: (state, items) => {
Vue.set(state, 'items', [items]);
}
},
actions: {
loadTriggeredTests: (context, data) => {
const newTriggeredTests = array1.map(item => {
return {
dateFormatted : moment(item.date).format('DD MMMM YYYY'),
explanationTest : testMapping.get(item.test)
}
});
context.commit("MUTATE_ITEMS", newTriggeredTests);
}
}
});
I've got a simple VueJS application which uses a Vuex store:
const store = new Vuex.Store({
state: {
organisatie: {}
},
mutations: {
loadOrganisatie (state, payload) {
state.organisatie = payload.organisatie;
console.log(payload.organisatie);
}
}
});
From one of my components I then load the organisation's data to the store as some other components on the page also need its data:
...
created() {
axios.get('/get/'+this.$route.params.orgId)
.then(response => {
this.$store.commit({
type: 'loadOrganisatie',
organisatie: response.data
})
}
...
But the commited state of my Vuex store remains an empty object:
The payload.mutation.organisatie in the devtools is filled with the proper data. But the state.organisatie stays an empty object.
Hope, it will work great for you
mutations: {
loadOrganisatie (state, payload) {
state.organisatie = Object.assign({},payload.organisatie);
console.log(payload.organisatie);
}
}
How can I watch for store values changes when params are used? I normally would do that via a getter, but my getter accepts a param which makes it tricky as I've failed to find documentation on this scenario or a stack Q/A.
(code is minimized for demo reasons)
My store.js :
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
let report = {
results: [],
};
export const store = new Vuex.Store({
state: {
broken: Object.assign({}, report),
},
results: (state) => (scan) => {
return state[scan].results
},
});
vue-component.vue :
computed: {
...mapGetters([
'results',
]),
watch: {
results(){ // How to pass the param ??
// my callback
}
So basically I would like to find out how to pass the param so my watch would work.
In my opinion, there is no direct solution for your question.
At first, for watch function, it only accept two parameters, newValue and oldValue, so there is no way to pass your scan parameter.
Also, your results property in computed, just return a function, if you watch the function, it will never be triggered.
I suggest you just change the getters from nested function to simple function.
But if you really want to do in this way, you should create a bridge computed properties
computed: {
...mapGetters([
'results',
]),
scan() {
},
mutatedResults() {
return this.results(this.scan);
},
watch: {
mutatedResults() {
}
}
}