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);
}
}
});
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.
Can someone please help me? I actually don't understand how to initiate localForage (getItem and setItem) in VueX. I have an array on component side, and need to copy it to indexedDB by VueX.
So I have mounted() and watch on the component and trying to activate them by using actions on VueX. Is this possible?
Here is the code on VueX:
enter image description here
and here component side:
enter image description here
enter image description here
export default new Vuex.Store({
state: {
totalTvCount: 10, // The TV inventory
notes: []
},
getters: {
totalTvCount: state => state.totalTvCount,// Here we will create a getter
notes: state => state.notes
},
mutations: {
incTv(state, amount){
state.totalTvCount += amount
} // Here we will create Jenny
},
actions: {
inc(context, amount) {
context.commit('incTv', amount)
}, // Here we will create Larry
setItems(){
localStorage.setItem("notes")
},
getItems(){
localStorage.getItem("notes").then(data => {
this.state.notes = data
})
}
}
Just add a mutation for setting the notes
setNotes(state, notes) {
state.notes = notes
}
then commit the mutation from your action:
getItems({commit}) {
localStorage.getItem("notes")
.then(data => commit('setNotes', data) )
}
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);
}
}
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)
I would like to assign setter methods via mapState. I currently use a workaround where I name the variable that I am interested in (todo) as a temporary name (storetodo) and then refer to it in another computed variable todo.
methods: {
...mapMutations([
'clearTodo',
'updateTodo'
])
},
computed: {
...mapState({
storetodo: state => state.todos.todo
}),
todo: {
get () { return this.storetodo},
set (value) { this.updateTodo(value) }
}
}
I would like to skip the extra step and define the getter, setter directly within mapState.
Why would I want to do this?
The normal approach would be use mapMutations/mapActions & mapState/mapGetters
without the computed get/set combination that I have illustrated above and to reference the mutation directly in the HTML:
<input v-model='todo' v-on:keyup.stop='updateTodo($event.target.value)' />
The getter/setter version allows me to simply write:
<input v-model='todo' />
You can't use a getter/setter format in the mapState
what you can try is directly return the state in your get() and remove mapState from the computed property
computed: {
todo: {
get () { return this.$store.state.todos.todo},
set (value) { this.updateTodo(value) }
}
}
Here is a related but not same JsFiddle example
This is my current workaround. Copied from my personal working project
// in some utils/vuex.js file
export const mapSetter = (state, setters = {}) => (
Object.keys(state).reduce((acc, stateName) => {
acc[stateName] = {
get: state[stateName],
};
// check if setter exists
if (setters[stateName]) {
acc[stateName].set = setters[stateName];
}
return acc;
}, {})
);
In your component.vue file
import { mapSetter } from 'path/to/utils/vuex.js';
export default {
name: 'ComponentName',
computed: {
...mapSetter(
mapState({
result: ({ ITEMS }) => ITEMS.result,
total: ({ ITEMS }) => ITEMS.total,
current: ({ ITEMS }) => ITEMS.page,
limit: ({ ITEMS }) => ITEMS.limit,
}),
{
limit(payload) {
this.$store.dispatch({ type: TYPES.SET_LIMIT, payload });
},
},
)
},
}
now you can use the v-model bindings. l
Another way of approaching that is using store mutations like below:
//in your component js file:
this.$store.commit('setStoretodo', storetodo)
Assuming you define setStoretodo in mutations of your vuex store instance (which is something recommended to have anyways):
//in your vuex store js file:
state:{...},
actions: {...}
...
mutations: {
setStoretodo(state, val){
state.storetodo = val
},
...
}
...
That keeps the property reactive as mapState will grab the updated value and it will be rendered automatically.
Surely, that's not as cool as just writing this.storetodo = newValue, but maybe someone will find that helpful as well.