vuex store doesn't update component - javascript

I'm new to vue, so I'm probably making a rookie error.
I have a root vue element - raptor.js:
const Component = {
el: '#app',
store,
data: {
productList: store.state.productlist
},
beforeCreate: function () {
return store.dispatch('getProductList', 'getTrendingBrands');
},
updated: function (){
console.log(111);
startSlider();
}
};
const vm = new Vue(Component);
Using this template
<div class="grid-module-single popular-products" id="app">
<div class="row">
<div class="popular-items-slick col-xs-12">
<div v-for="product in productList">
...
</div>
</div>
</div>
My store is very simple store/index.js:
import Vue from 'vue';
import Vuex from 'vuex';
import model from '../../utilities/model';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
productlist: []
},
mutations: {
setProductList(state, data) {
state.productlist = data;
}
},
actions: {
getProductList({ commit }, action) {
return model.products().then(data => commit('setProductList', data));
}
}
});
In my vuex devtool, I can see, that the store is being updated
https://www.screencast.com/t/UGbw7JyHS3
but my component is not being updated:
https://www.screencast.com/t/KhXQrePEd
Question:
I can see from the devtools, that my code is working. The store is being updated with data. My component is not being updated,however. I thought it was enough just to add this in the data property on the component:
data: {
productList: store.state.productlist
}
but apparently the data object doesn't seem to be automatically synced with the store. So either I'm doing a complete vue no-no somewhere, or I need to tweak the code a bit. Anyway can anyone help me in the right direction.
Thanks a lot.

UPDATE
Figured it out myself. Just had to replace the components data part with a computed method:
data:
data: {
productList: store.state.productlist
}
and replace it with.
computed: {
productList () {
return store.state.productlist;
}
},

data only work once on component before render, so you can use computed instead.
like above answer, or you can use mapstate
import {mapState} from 'vuex'
...
computed: mapState({
productList: state => state.productList
})

First - use getter to do this mapGetters, also you need to watch this property somehow, you can set store subscription or just with watch method trough component.
this.$store.subscribe((mutation, state) => {
if (mutation.type === 'UPDATE_DATA') {
...
}
}

You are calling the store into the productList data property in the wrong way.
You can try it:
data: {
productList: $store.state.productlist
}
Otherwise you have to import store in each component that are using the store.

Related

Vue - composition API Array inside reactive is not updating in the DOM

I have an Array of posts inside reactive() and I want it to be updated onMounted.
How can I do this?
TEMPLATE:
<q-card>
<board-item-list :items="items" v-if="items.length" />
<board-empty v-else />
</q-card>
SCRIPT
import { reactive, onMounted } from "vue";
import { posts } from "./fake-data.js";
export default {
setup() {
let items = reactive([]);
...
onMounted(() => {
// to fill the items with posts.
items.values = posts; // I tried this not working
items = posts; //I tried this not working
console.log(items);
});
...
return {
...
items,
};
},
};
Try to use ref instead of reactive or define items as nested field in a reactive state like :
import { reactive, onMounted } from "vue";
import { posts } from "./fake-data.js";
export default {
setup() {
let state= reactive({items:[]});
...
onMounted(() => {
state.items = posts;
console.log(state.items);
});
...
return {
...
state,
};
},
};
in template :
<q-card>
<board-item-list :items="state.items" v-if="state.items.length" />
<board-empty v-else />
</q-card>
if you want to get rid of state in the template you could use toRefs:
import { reactive, onMounted,toRefs } from "vue";
import { posts } from "./fake-data.js";
export default {
setup() {
let state= reactive({items:[]});
...
onMounted(() => {
state.items = posts;
console.log(state.items);
});
...
return {
...toRefs(state),//you should keep the 3 dots
};
},
};
composition api is very frustrating, vue2 was much better.
composition api is much more complex with basically no tangible benefit
composition api has so many reactivity problems like the above, proxy objects, pointless wrapper, total waste of space imho, vue3 is a great example of developers wrecking a good project.

Vue md-input with Vuex updating only in one direction

I have a Vue app with an input element bound to a like this:
<template>
<input v-model="this.$store.state.myvalue"/>
</template>
and VueX store/index.js:
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
myvalue: null
},
mutations: {},
actions: {},
modules: {}
});
When I modify myvalue with Vue devtools, the input's value changes too, but when I change the value in the input field, the state variable does not change. What am I doing wrong? I'm new to VueX.
Although it's not suggested to use vuex state directly bound with view layer, instead vuex is better to use for business logic, you can achieve changing the state on user input by below mentioned ways:
[1] two-way data binding: use v-model directive & bind the state in
it. On user input, the state will be updated. On changing state
programmatically, the element's value will be updated & reflect on
dom.
.vue file
<template>
<input v-model="$store.state.myvalue"/>
</template>
[2] manually create two-way data-binding.
.vue file
<template>
<input :value="getMyValue" #input="handleInput"/>
</template>
<script>
export default {
methods: {
handleInput (value) {
this.$store.commit('UPDATE_MY_VALUE', { value })
}
},
computed: {
getMyValue () {
return this.$store.state.myvalue
}
}
}
</script>
store file
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
myvalue: null
},
mutations: {
UPDATE_MY_VALUE (state, { value }) {
state.myvalue = value
}
},
actions: {},
modules: {}
});
when I change the value in the input field, the state variable does not change.
It does change, Dev tools just don't show the change. You can validate by changing the template to this:
<template>
<div>
<input type="text" v-model="$store.state.myvalue">
<div>{{ $store.state.myvalue }}</div>
</div>
</template>
But you should not mutate Vuex state like this! Vuex allows it but it's not recommended. Reason is your state changes should be traceable (easier to find which component changed the state and when). That's why Vuex recommends changing the state only by using mutations. Mutation is basically a function which is called when state change is needed.
Best way to do 2-way data binding against Vuex state is using computed properties with getter/seter like this:
<template>
<div>
<input v-model="myvalue">
<div>{{ myvalue }}</div>
</div>
</template>
<script>
export default {
name: "HelloWorld",
computed: {
myvalue: {
get: function() {
return this.$store.state.myvalue;
},
set: function(value) {
this.$store.commit("change_myvalue", value);
}
}
}
};
</script>
You need to define a mutation in your store to make it work like this:
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
myvalue: ""
},
mutations: {
change_myvalue(state, value) {
state.myvalue = value
}
},
actions: {},
modules: {}
});
You can read more about mutations here

Should we use v-model to modify Vuex store?

Hello I am beginner in Vue and I do have a problem that's really bugging me.
I am wondering should we use v-model directive to modify vuex store? Vuex says that we should modify vuex store only by mutations but v-model makes everything easier and shorter.(I am asking because i couldn't find clear answer)
https://vuex.vuejs.org/guide/forms.html
When using Vuex in strict mode, it could be a bit tricky to use v-model on a piece of state that belongs to Vuex.
The "Vuex way" to deal with it is binding the <input>'s value and call an action on the input or change event.
Be sure to check out the simple "Two-way Computed Property" example on that page:
<input v-model="message">
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
I think another good option which hasn't been mentioned in any answer here is to use vuex-map-fields. In fact, the library author has written a very nice explanation for the library's usefulness. As per its GitHub page, to use the library you can do something like this:
In your Vuex Store, you can have a snippet similar to this:
import Vue from 'vue';
import Vuex from 'vuex';
import { getField, updateField } from 'vuex-map-fields';
Vue.use(Vuex);
export default new Vuex.Store({
// ...
modules: {
fooModule: {
namespaced: true,
state: {
foo: '',
},
getters: {
getField,
},
mutations: {
updateField,
},
},
},
});
And in your component code, you can have something along the lines of this:
<template>
<div id="app">
<input v-model="foo">
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields';
export default {
computed: {
// `fooModule` is the name of the Vuex module.
...mapFields('fooModule', ['foo']),
},
};
</script>
Additional examples for various use cases are shown in the library's GitHub repository that I linked to in the first sentence of this answer.
Above solution can also implemented with mutations:
<template>
<input v-model="message">
</template>
<script>
import { mapMutations, mapState } from 'vuex';
export default {
computed: {
...mapState({messageFromStore: 'message'}),
message: {
get() {
return this.messageFromStore;
},
set(value) {
this.updateMessage(value);
}
}
},
methods: {
...mapMutations('updateMessage')
}
};
</script>
My Solution to this was to use a getter to set value and #input to call the mutation.
<input
type="text"
:value="$store.getters.apartmentStreet"
#input="value => $store.commit('apartmentValue', { handle: 'street', value })"
>
getters.js:
export default {
apartmentStreet: state => state.apartment.street,
};
mutations.js
export default {
apartmentValue(state, payload) {
let oldValue = state.apartment[payload.handle];
let newValue = payload.value;
if (newValue !== oldValue) state.apartment[payload.handle] = payload.value;
}
};
If you use this method be sure to check which event you want.
I use this solution.
data() {
return {
formData: {
username: '',
email: '',
bio: {
firstName: '',
lastName: ''
},
games: ['civ4', 'caesar3', 'homeworld', 'cataclysm'],
}
}
},
computed: {
...mapGetters({ //or mapState
user: 'users'
})
},
watch: {
user(newValue) {
this.formData.username = newValue.name;
this.formData.email = newValue.email;
this.formData.bio.firstName = newValue.bio.firstName;
this.formData.bio.lastName = newValue.bio.lastName;
this.formData.games = newValue.games.map(x=> { return x });
}
},
beforeCreate: fucntion() {
this.$store.dispatch('getUser');
}
And then you just regularly use v-model.
It is important to make deep copy of object from store, like using map for array, and how i did stuff with object inside.
And, also you need to have initiated this user object in store also, with empty fields.
Yes you can but is not the best practice.
As the documentation say the state should be updated only inside mutation to keep the control over the state.
But if you really want to do it you can with:
v-model="$store.state.yourProperty"

Vuex getter to populate a component using v-for

I'm building a vue2 component, with a vuex store object. The component looks like this:
<template>
<ul id="display">
<li v-for="item in sourceData()">
{{item.id}}
</li>
</ul>
</template>
<script>
export default {
mounted: function () {
console.log('mounted')
},
computed: {
sourceData: function() {
return this.$store.getters.visibleSource
}
}
}
</script>
The store is populated via an ajax call at the beginning of the process, in the main javascript entry:
new Vue({
store,
el: '#app',
mounted: function() {
this.$http.get('/map/' + this.source_key + '/' + this.destination_key)
.then(function (response) {
store.commit('populate', response.data)
})
.catch(function (error) {
console.dir(error);
});
}
});
I'm not seeing any errors, and when I use the Vue devtools explorer I can see that my component's sourceData attribute is populated with hundreds of items. I'd expect that once this data is populated, I'd see a bunch of li rows with item.id in them appear on the page.
But despite no errors and apparently good data in the component, I am not seeing the template render anything.
Do I need to use some sort of callback to fire the component after the vuex store is populated?
EDIT: adding store code:
import Vue from 'vue';
import Vuex from 'vuex';
import { getSource, getDestination } from './getters'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
field_source: [],
field_destination: []
},
getters: {
visibleSource: state => {
// this just does some formatting
return getSource(state.field_source)
},
visibleDestination: state => {
return getDestination(state.field_destination)
}
},
mutations: {
populate(state, data) {
state.field_source = data.source
state.field_destination = data.destination
}
}
})
EDIT2: Maybe it's not a problem with the v-for-- I don't see anything from the template being rendered, not even the main ul tag, which I'd expect to see (empty) even if there was a problem further in the script.
sourceData is a computed property, not a method. You don't need to invoke it. Don't use it like v-for="item in sourceData()", use it like v-for="item in sourceData".
Other than that, on your 'populate' mutation you are overwritting the observed/reactive objects.
Either use Vue.set():
mutations: {
populate(state, data) {
// was state.field_source = data.source
Vue.set(state, 'field_source', data.source);
// was state.field_destination = data.destination
Vue.set(state, 'field_destination', data.destination);
}
}
Or push all elements to the existing, observed/reactive, arrays:
mutations: {
populate(state, data) {
// was state.field_source = data.source
state.field_source.push(...data.source);
// was state.field_destination = data.destination
state.field_destination.push(...data.destination);
}
}

How to design a store in Vuex to handle clicks in nested, custom components?

I'm trying to design a store to manage the events of my Vuex application. This far, I have the following.
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const state = { dataRows: [], activeDataRow: {} };
const mutations = {
UPDATE_DATA(state, data) { state.dataRows = data; state.activeDataRow = {}; },
};
export default new Vuex.Store({ state, mutations });
I'm going to have a number of list items that are supposed to change the value of the data in the store when clicked. The design of the root component App and the menu bar Navigation is as follows (there will be a bunch of actions in the end so I've collected them in the file actions.js).
<template>
<div id="app">
<navigation></navigation>
</div>
</template>
<script>
import navigation from "./navigation.vue"
export default { components: { navigation } }
</script>
<template>
<div id="nav-bar">
<ul>
<li onclick="console.log('Clickaroo... ');">Plain JS</li>
<li #click="updateData">Action Vuex</li>
</ul>
</div>
</template>
<script>
import { updateData } from "../vuex_app/actions";
export default {
vuex: {
getters: { activeDataRow: state => state.activeDataRow },
actions: { updateData }
}
}
</script>
Clicking on the first list item shows the output in the console. However, when clicking on the second one, there's nothing happening, so I'm pretty sure that the event isn't dispatched at all. I also see following error when the page's being rendered:
Property or method "updateData" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
I'm very new to Vuex so I'm only speculating. Do I need to put in reference to the updateData action in the store, alongside with state and mutations? How do I do that? What/where's the "data option" that the error message talks about? Isn't it my components state and it's properties?
Why the error
You are getting the error, because when you have <li #click="updateData"> in the template, it looks for a method updateData in the vue component which it does not find, so it throws the error. To resolve this, you need to add corresponding methods in the vue component like following:
<script>
import { updateData } from "../vuex_app/actions";
export default {
vuex: {
getters: { activeDataRow: state => state.activeDataRow },
actions: { updateData }
},
methods:{
updateData: () => this.$store.dispatch("updateData")
}
}
</script>
What this.$store.dispatch("updateData") is doing is calling your vuex actions as documented here.
What/where's the "data option"
You don't have any data properties defined, data properties for a vue component can be used, if you want to use that only in that component. If you have data which needs to be accessed across multiple components, you can use vuex state as I believe you are doing.
Following is the way to have data properties for a vue component:
<script>
import { updateData } from "../vuex_app/actions";
export default {
date: {
return {
data1 : 'data 1',
data2 : {
nesteddata: 'data 2'
}
}
}
vuex: {
getters: { activeDataRow: state => state.activeDataRow },
actions: { updateData }
},
methods:{
updateData: () => this.$store.dispatch("updateData")
}
}
</script>
You can use these data properties in the views, have computed properies based on it, or create watchers on it and many more.

Categories

Resources