View doesn't get updated after updates in store Vue js - javascript

I have Store, where I have actions and mutations
My action loadProducts calls the mutation setProducts, where I set values in state and it works fine
const actions = {
loadProducts({ commit }, accountId ) {
Products.where( { accountId: accountId } ).all().then((products) => {
commit("setProducts", {accountId: accountId, products: products.data})
});
},
}
const mutations = {
setProducts(state, {accountId, products}) {
Vue.set(state, 'products', products);
}
}
And then in my component I get the data, using mapGetters and computed
computed: {
...mapGetters([
'products'
]),
products2() {
console.log(this.products)
return this.products;
}}
Everything works, but then I try to update the data, it also works, I get updated state - in Vuex console I can see my updates state and new data, but the view itself won't be updated - so i Have to reload the page, to see the data appears.
I also do it via actions, mutations and getters, in mutations it looks like this:
replace() {
Vue.set(state, key, val);
}

Should you be using mapState instead of mapGetters to directly map the state products to your component? Do you have a getter called products that does some extra work?
...mapState([
'products'
]),

Related

What is the best way to save async data in Vue?

I have a following store module:
const state = {
user: {}
}
const mutations = {
saveData(state, payload) {
state.user = payload
}
}
const actions = {
async loadData({ commit }, payload) {
try {
const res = await axios.get('https://api.example.com/user/1')
commit('saveData', res.data.data)
} catch(e) {
console.log(e)
}
}
}
const getters = {
getData(state) {
return state.user
}
}
Now what's the best way to save the data in component? Is it using watch
import { mapGetters } from 'vuex'
export default {
data() {
return {
user: {}
}
},
computed: {
...mapGetters({
getData
})
},
watch: {
'$store.state.users.users'() {
this.user = this.getData
}
}
}
... or store.subscribe?
import { mapGetters } from 'vuex'
export default {
data() {
return {
user: {}
}
},
computed: {
...mapGetters({
getData
})
},
created() {
this.$store.subscribe((mutation, state) => {
if(mutation.type === 'saveData') {
this.user = this.getData
}
})
}
}
Since you already know about store mapping I suppose you try to have some kind of edit form where you need the actual data taken from the database and also the ability to change this data to later send it back to the database.
You don't need a getter to have a simple reference to a store item. You will be very fine with mapState in your component:
{
computed: {
...mapState({
user: state => state.user,
}),
}
}
So as soon as user changed in the store your component will know about it. And here you can update the object you're editing. Let's rename it to edit to avoid collision:
{
data() {
return {
edit: {},
}
},
computed: {
...mapState({
user: state => state.user,
}),
},
watch: {
user: {
immediate: true,
handler(user) {
this.edit = { ...user }
},
},
},
}
Now edit is updated accordingly even if the component was mounted after the store item was updated (thanks to immediate option), and you can safely modify it in your component without any impact on the store reference.
P.S. Have to mention that in this implementation if you want to have reactivity on fields within edit object, then you need to update the whole edit object on each it's field update like this: this.edit = {...this.edit, [prop]: value}. But if you want it to be the natural Vue way, then first you need to initialize edit with actual object structure, and in the watcher for user perform something like Object.assign(this.edit, user).
It's preferable to use computed properties to access store data and keep it reactive.
It's possible to create a computed property using mapGetters as you do in the shared snipped, however, taking into account the getter is simply returning user from state, I don't think you need a getter at all, you can simply map the value from state by using mapState helper. In this way the component would be simplified to something like as follows:
import { mapState } from 'vuex'
export default {
computed: {
...mapState([
'user'
])
}
}
With the above approach you can reference user as this.user in the component's methods or simply as user in template of the component. Also, since the getter is out of use, you can delete the getter definition from the store (unless you are using it anywhere else).

Load state with async action Vuex

I can't realize how to get it works.
I'm trying to load a propertie (productos) on my data() which has to catch the value from a state.
My component:
data () {
return {
productos: this.$store.state.mStock.productos
}
},
created() {
this.$store.dispatch('fetchProductos')
}
At this point i think it's okay, when i load my component i dispatch my action to load the state on the store.
I think the problem is that the way i fill the state is ASYNC
Store:
import StockService from '#/services/StockService'
export const moduleStock = {
strict: false,
state: {
productos: []
},
mutations: {
setProductos (state, payload) {
state.productos = payload.productos
}
},
actions: {
async fetchProductos ({commit}, payload) {
const resp = await (StockService.getProductos())
var productos = resp.data
commit('setProductos', {productos: productos})
}
}
}
When i load my component, the propertie "productos" on the data() is null, however if i see the 'state.productos' from the Vuex devtools, it has the data!
I'm messed up.
The data() method is only run once.
This might seem to work if when the component and the vue store use the same object instance, but doesn't work in this case because a new array instance is assigned in the store while the component stil has the previous instance (the empty array)
Use computed properties. I recommend using the mapState() helper:
computed: mapState({
productos: state => state.mStock.productos
})
without mapState you'd write:
computed: {
productos() {
return this.$store.state.mStock.productos
}
}

How do I export a string to another Vue file?

I have a masterData.js file that is a store for my master data, in short the file reads my mongo db data & sends it to other project components. I created a function to export the string in the masterData.js file as:
/ ***************************** MUTATIONS
const mutations = {
exportColumns (payload) {
Object.keys(payload[0]).map(x => { return x; });
}
}
Where payload will store all the rows and payload[0] holds the value of column header names. The output of this chunk of code is like this:
["_id","businessAreaName","businessAreaDisplay","councilDisplay","councilID"]
I want to transfer the values to masterData.vue file. My code on masterData.Vue is:
importColumns ()
{
let payload = {
vm: this,
mutation: 'masterData/exportColumns'
};
}
What else should I add to to check whether the column names are received or not?
If you're trying to access the data in your store from within a component, then you'll want to either just map the state to the component or map a getter to the component. Mutations are used by components (or by actions) to modify the state of the store. So instead you would do something like...
//masterData.js
//assuming this gets rolled up as a module called masterdata to the primary store
//store for payload state
const state = {
payload: null,
}
//allows payload to be set -- not sure how you are retrieving the payload but you can use this to store it however you get it
const mutations = {
setPayload (state, payload) {
state.payload = payload
}
}
//get just the columns
const getters = {
getColumns (state) {
Object.keys(state.payload[0]).map(x => { return x; })
}
}
export default {
state,
mutations,
getters,
}
Then
//masterData.vue
<template>
//...
</template>
<script>
import { mapGetters, mapState } from 'vuex'
export default {
computed: {
//I believe getting state from a store module requires a function like this
...mapState({
payload: function(state) {
return state.masterdata.payload
},
}),
//I think for getters you can just reference the method and it will find it
...mapGetters([
'getColumns',
])
},
}
</script>
This is how you import stuff in a single file component.
<template>
<!-- html stuff -->
</template>
<script>
import Mutations from 'yourModule.js'
export default {
name: 'YourComponent',
props: {},
data(){
return {
foo: 'foo'
}
},
methods{
mymethod() {
Mutations.exportColumn(this.foo);
},
}
}
</script>

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