Updating state of vuex array when one item has some changes - vue.js

I have an array called cases in my vuex store.
I want to update the array with the new content when I update a few fields within an existing item in the array.
I thought I could do something like this in my mutation but doesn't work and get the error typeError: undefined is not an object (evaluating 'state.objects.find') —
EDIT_CASE (state, payload) {
const item = state.objects.find(item => item.id === payload.recordId);
Object.assign(item, payload.case_status);
my array is as follows:
[
{
"case_name": "Laptop not working",
"case_status": "live",
"case_summary": "This is some summary content",
"createdBy": "zippy",
"createdDate": "2018-06-21T15:20:22.932Z",
"id": "-LFXvk9yY5c-O8yIdf8k"
},
{
"case_name": "Something else",
"case_status": "live",
"case_summary": "This is some summary content",
"createdBy": "zippy",
"createdDate": "2018-06-21T15:20:22.932Z",
"id": "-STVvk9yY5c-O3yiTy8k"
}
]
I also think from what I have read Vue does not observe changes within arrays so it may be I'm going completely the wrong way with this, and need to remove and then re-add the array item?
Basically I have a list, I make a change to my backend, now I want that list to reflect the changes I have made through updating the cases state, can anyone help?

There is no array issue with your example because you try to change an object property - not array element reference.
The problem is in Object.assign(item, payload.case_status); - you should provide an object not just a field.
(Also you said that array called cases but example has objects, maybe this is problem too);
So this should work:
EDIT_CASE (state, payload) {
const item = state.objects.find(item => item.id === payload.recordId);
Object.assign(item, payload);
}
The error:
undefined is not an object
I think, it is related to Object.assign because you pass field to it which is undefined probably.
P.S. There is small example to help you understand when array issue appears and when everything works fine. See code comments :)
new Vue({
el: "#app",
data: {
todos: [
{ text: "Learn JavaScript" },
{ text: "Learn Vue" },
{ text: "Play around in JSFiddle" },
{ text: "Build something awesome" }
]
},
methods: {
// work because object property is reactive
changeItemProperty() {
this.todos[3].text = "changedItemProperty";
},
// same reason, properties are reactive
changeItemWithAssign() {
Object.assign(this.todos[3], { text: "changedItemWithAssign" });
},
// does not work, can not track changes in array
// also this will break all attempts to change TEXT property in UI
// because property becomes not reactive after this due to new object
// try to changeItemProperty or changeItemWithAssign - does not work!
// changeItem2 will fix it :)
changeItem() {
this.todos[3] = { text: "changedItem" }
},
// works
changeItem2() {
Vue.set(this.todos, 3, { text: "changedItem2" });
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<div v-for="todo in todos" :key="todo.text">
{{todo.text}}
</div>
<button #click="changeItemProperty">changeItemProperty(works)</button>
<button #click="changeItemWithAssign">changeItemWithAssign(works)</button>
<button #click="changeItem">changeItem(does not work!)</button>
<button #click="changeItem2">changeItem2(works)</button>
</div>

JavaScript (not specific to Vue) can not detect setting the value of an Array item directly by index arr[3] = 'stop';
It also can not detect adding a new key or deleting an existing key from an Object.
You must be defining the initial state of the store, e.g.
const store = new Vuex.Store({
state: {
objects: []
},
mutations: {
EDIT_CASE (state, payload) {
const index = state.objects.findIndex(item => item.id === payload.id);
if (index !== -1) state.objects.splice(index, 1, payload);
}
}
})

you need update your Array
const store = new Vuex.Store({
state: {
objects: [
{id: 1, someProps: 'blablabla'},
{id: 2, someProps: 'ololololo'}
]
},
mutations: {
EDIT_CASE (state, data) {
const index = state.objects.findIndex(item => item.id === data.id);
state.objects[index].someProps = data.newPropsValue;
//this is for vue reaction
state.objects.push('dog-nail');
state.objects.splice(-1,1);
}
})

You can use Vue.set to make the new object reactive
const store = new Vuex.Store({
state: {
objects: []
},
mutations: {
EDIT_CASE (state, payload) {
const index = state.objects.findIndex(item => item.id === payload.id);
if (index !== -1) {
Vue.set(state.objects, index, payload);
}
}
}
});

I had a problem like that and I solved like below:
// in the component
computed: {
data: {
get() {
this.$store.commit('updateData', this.$store.state.data)
return this.$store.state.data;
},
}
}
// in the store
mutations: {
updateData(state,item){
store.state.data = item;
}
}
yes, it's very amazing.

Related

how to invoke connection between pushed state and form input

I have a form and i use v-model for that to connect it to computed , and computed use get and set with object in VueX ,
when form is submitted that object will pushed into main array , the problem is that , even after push the connection between form input and pushed object in array will not disconnect and when new form submited the old will change ,
this is computed that v-modeled whith text input
computed: {
name: {
get() {
return this.$store.state.item.name
},
set(value) {
this.$store.commit('mut_up_name', value)
},
},
and this is vuex mutations
export const mutations = {
mut_up_name(state,v){
state.item.name=v
},
and this code push obj to main array
add_item(state) {
let a={...state.item}
state.items.push(a)
},
how can i envoke connection between pushed state & input
It is better to avoid changing the state directly inside of vuex actions and if you would like to change the value of the input, use #input instead and dispatch your actions from there. If you would like mutate multiple actions, then you can take a look from my approach:
Template:
<template>
<some-input-component :value="name" #input="inputHandler($event)"/>
</template>
Script:
computed: {
name() {
return this.$store.state.item.name;
},
},
methods: {
inputHandler(e) {
this.$store.dispatch('add_item', e);
},
},
in the vuex:
state: {
item: {
name: '',
},
someArray: [],
},
actions: {
add_item: ({ commit }, e) => {
commit('mutate_name', e);
commit('push_item', e);
}
},
mutations: {
mutate_name: (state, value) => {
state.item.name = value;
},
push_item: (state, obj) => {
state.someArray.push(obj);
},
},

Vue.js, Vuex; component view not reacting when data in the Vuex store is mutated (with mysterious exception)

This is my first foray into using Vuex, and I'm baffled by a problem relating to a searchResults array, managed in the $store, specifically why a SearchResults view component doesn't seem to be reacting when the store is mutated.
I have a search form component which, when submitted, invokes a search function (mixin), dispatches an action which updates the searchResults array in the store, and then loads ViewSearchResults.vue, where the search results are displayed - and this is working.
Now, ViewSearchResults.vue also includes the search form component, and when a subsequent search is run, again the search function runs successfully, the store is updated accordingly, however ViewSearchResults.vue is not reacting to the change in the store, e.g., update lifecycle doesn't fire, so the new search results are unavailable
... and then in my debugging journey I discovered that by adding a reference to the store in the template - e.g., {{ this.$store.state.searchResults.length }}, the view updates, the new data is available, and any subsequent searches successfully update the view.
None of my experience with Vue.js so far explains this. Can someone please shed some light on this, and how I can realize the desired results without polluting my markup?.
Many thanks in advance.
relevant excerpt of my search mixin:
export default {
created: function() {},
methods: {
doSearch: function(term) {
const searchTerm = term.toLowerCase();
this.$store.dispatch("setSearchTerm", term);
let searchResults = [];
// SNIP: search (iterate) a bunch of .json data ...
searchResults.push(searchResult); // searchResults array CONFIRMED √
this.$store.dispatch("setSearchResults", searchResults);
}
}
}
relevant excerpt of the store:
export default new Vuex.Store({
strict: true,
state: {
searchTerm: "",
searchResults: [],
},
mutations: {
setSearchTerm(state, payload) {
state.searchTerm = payload;
},
setSearchResults(state, payload) {
console.log(payload); // √ confirmed: updated array is there
state.searchResults = payload;
console.log(state.searchResults); // √ confirmed: updated array is there
}
},
getters: {
},
actions: {
// dispatched in the search mixin
setSearchTerm(context, payload){
context.commit("setSearchTerm", payload);
},
setSearchResults(context, payload) {
context.commit("setSearchResults", payload);
}
},
modules: {
}
})
... and ViewSearchResults.vue (relevant excerpts):
// IF I LEAVE THIS IN, BOB'S YOUR UNCLE ... WITHOUT IT, THE VIEW DOESN'T REACT
<div style="display: none;">this.$store.state.searchResults: {{ this.$store.state.searchResults.length }}</div>
<ul class="search-results">
<li v-for="(imgObj, ix) in searchResults" :key="ix">
<img :src="require('#/assets/img/collections/' + imgObj.path + '/' + imgObj.data + '/' + imgObj.imgFile)" alt="" />
</li>
</ul>
export default {
components: {
// 'app-search' occurs elswhere in the app, but when submitted, loads this ViewSearchResults, search component still present
'app-search': SearchForm
},
props: {
},
data() {
return {
searchTerm: "",
searchResults: []
}
},
created: function() {
// only becuz refresh
if (!this.searchTerm) {
this.searchTerm = this.$route.params.searchTerm;
}
console.log(this.$store.state.searchResults.length); // 0 if refreshed, ERGO:
this.$store.state.searchResults.length ? this.searchResults = this.$store.state.searchResults : this.searchResults = JSON.parse(localStorage.getItem("searchResults"));
console.log(this.searchResults); // searchResults √
},
updated: function() {
// ?!?!?! WHY DOES THIS FIRE ONLY IF I LEAVE THE REFERENCE TO THE STORE IN THE TEMPLATE? {{ this.$store.state.searchResults.length }}
this.$store.state.searchTerm ? this.searchTerm = this.$store.state.searchTerm : this.searchTerm = localStorage.getItem("searchTerm");
this.$store.state.searchResults.length ? this.searchResults = this.$store.state.searchResults : this.searchResults = JSON.parse(localStorage.getItem("searchResults"));
},
computed: {
},
mounted: function() {},
mixins: [ Search ]
}
Many thanks again for any insight.
Whiskey T.
You've got nothing updating in your component so it won't need to execute the update hook.
It seems you actually want your component to be driven by the values in the store.
I would set it up as recommended in the Vuex guide
computed: {
searchResults () {
return this.$store.state.searchResults
}
},
created () {
this.doSearch(this.$route.params.searchTerm)
}
You could also use the mapState helper if you wanted.
computed: mapState(['searchResults']),
The part where you load data from localstorage should be done in your store's state initialiser, ie
let initialSearchResults
try {
initialSearchResults = JSON.parse(localStorage.getItem('searchResults'))
} catch (e) {
console.warn('Could not parse saved search results')
initialSearchResults = []
}
export default new Vuex.Store({
strict: true,
state: {
searchTerm: "",
searchResults: initialSearchResults
},

Vue-tables-2(vuex) reactivity not working

I've several components using vue-tables-2 but one of them is not updating the table until I change the route.
component
<template>
//..
<div class="table-responsive" >
<v-client-table ref="table" name="vCardTable"
:data="vCardTableData.data"
:columns="vCardTableData.headers"
:options="vCardTableData.options"/>
</div>
//..
</template>
<script>
import { mapState } from "vuex";
import { mapGetters } from "vuex";
export default {
name: "VCard",
computed: {
...mapState("commons", ["user"]),
...mapGetters({ vCardTableData: "vCard/vCardTableData" })
},
mounted() {
var self = this;
self.$nextTick(() => {
self.$store.dispatch("vCard/getVCards"); <-- GET TABLE DATA
});
}
};
</script>
store
const state = {
vCardTableData: {
data: [],
headers: [
//..
],
options: {
filterable: false,
preserveState: false,
headings: {
//..
},
texts: {
//..
},
pagination: {
dropdown: true,
},
templates: {
//..
},
},
}
}
const getters = {
vCardTableData: state => state.vCardTableData
}
const actions = {
getVCards({commit, dispatch}) {
return api.request("get", "getvcards").then(response => {
setTimeout(() => {
commit("setVCardTableData", response.data.vcards);
}, 300);
}).catch(error => {
console.log(error);
});
}
}
const mutations = {
clearTableData: (state) => {
if (state.vCardTableData.data) {
state.vCardTableData.data = [];
}
},
setVCardTableData : (state, vCardTableData) => state.vCardTableData.data = vCardTableData
}
As you can see in this image the table has data:
But the view is refreshed when the route changes:
02/05/2018
Well now I've seen that if I modify the state directly in the component with promises it works:
this.$store.dispatch("vCard/getVCards", []).then((responseData)=>{
this.$store.state.vCard.vCardTableData.data = responseData;
});
Does anyone know why?
Thank you
My last answer was wrong, I did not remember that I had changed the vuex parameter of the table to false. I don't know why but doing a push it works:
setVCardTableData : (state, vCardTableData) => {
vCardTableData.forEach(tableData => {
state.vCardTableData.data.push(tableData);
});
}
This is a probably a reactivity issue. (See https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats for detailed explanation.)
Changing how you set the object value in your mutation to this should solve the problem
setVCardTableData: (state, vCardTableData) => state.vCardTableData = {
...state.vCardTableData,
data: vCardTableData
}
Basically, this creates a new object so that Vue knows that the object has been updated. In Javasript, object is passed by reference, meaning that vCardTableData don't store the object, it stores the reference to the object. You could think of it as a pointer/address that points to the object in memory. When you change a child property in the object, the reference remains unchanged, so Vue does not know that the object has been updated. Creating a new object makes sure that the object reference is updated.
This is also explained in Mutations Follow Vue's Reactivity Rules
in https://vuex.vuejs.org/en/mutations.html
I have had similar issues. Like others have already mention it is probably a reactivity problem. You can use Vue.set() to ensure that your properties are reactive when setting the state values.
setVCardTableData: (state, vCardTableData) => {
Vue.set(state.vCardTableData, 'data', vCardTableData);
}
Check the official documentation for this method.
Since it's a deep object, you need to use Object.assign in your mutation
setVCardTableData: (state, vCardTableData) => Object.assign(state.vCardTableData.data, vCardTableData)

How to update Vuex store from v-model input in case of v-for

I've say 10 objects in an array like
policies = [{name:'a',text:''},{name:'b',text:''},....]
They're iterated using v-for to show label A: Inputbox with text property binded as v-model.
I want to trigger a mutation whenever a policy's text changes in v-model.
Here's the fiddle link for it.
https://jsfiddle.net/dmf2crzL/41/
We assume you want to use v-model for a 2-way binding along with Vuex store.
Your problem is that you want Vuex store in strict mode.
const store = new Vuex.Store({
// ...
strict: true
})
so all of your mutation should go through Vuex store and you can see it in Vue.js devtools.
Method 1: We can avoid the Vuex error by using the cloned object and use watcher to commit the mutation.
const store = new Vuex.Store({
strict: true,
state: {
formdata: [
{ label: 'A', text: 'some text' },
{ label: 'B', text: 'some other text' },
{ label: 'C', text: ' this is a text' }
]
},
mutations: {
updateForm: function (state, form) {
var index = state.formdata.findIndex(d=> d.label === form.label);
Object.assign(state.formdata[index], form);
}
}
});
new Vue({
el: '#app',
store: store,
data () {
return {
//deep clone object
formdata: JSON.parse(JSON.stringify(this.$store.state.formdata))
};
},
computed: {
formdata() {
return this.$store.state.formdata
}
},
watch: {
formdata: function(form)
this.$store.commit('updateForm', form);
}
}
})
Method 2: You can use computed get/set to commit your mutation as per the vuex doc
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
another way that I found useful:
replace the v-model to a (v-on) function
that function triggers a mutation
the mutation ("function" in the store) change a value in state
a getter ("computed" in the store) "listens" to the change in the property value and changes accordingly.
this is an example of how to filter cards with Vuex (instead of v-model:
input that triggers a function "updateFilter":
<input type="text" placeholder="filter" v-on:input='updateFilter'>
a function (method) that triggers a mutation (commit):
methods: {
updateFilter(event){
this.$store.commit('updateFilter', event.target.value);
}
in the store.js, a mutation that changes data (state):
mutations: {
updateFilter (state, filter) {
state.filter = filter;
},
the state:
state: {filter: ""}
and the getter (computed) that "listens" to the change in the state.
getters: {
filteredGames: state => {
//your filter code here
return filtered;
})
},
and finally, the component that needs to be filtered has this computed (getter):
computed: {
filtered() {
return this.$store.getters.filteredGames;
}
Mine library vuex-dot simplifies reactivity (and, sure, v-model) usage on vuex store
https://github.com/yarsky-tgz/vuex-dot
<template>
<form>
<input v-model="name"/>
<input v-model="email"/>
</form>
</template>
<script>
import { takeState } from 'vuex-dot';
export default {
computed: {
...takeState('user')
.expose(['name', 'email'])
.dispatch('editUser')
.map()
}
}
</script>

Vuex filter state

I'm at my first app in Vuejs Vuex.
I can not find the best way to filter a state.
store/index.js
state: {
projects: []
},
mutations: {
SET_PROJECT_LIST: (state, { list }) => {
state.projects = list
}
},
actions: {
LOAD_PROJECT_LIST: function ({ commit }) {
axios.get('projects').then((response) => {
commit('SET_PROJECT_LIST', { list: response.data})
}, (err) => {
console.log(err)
})
}
}
in the component:
computed: {
...mapState({
projects
})
}
At this point I have a list of my projects. Good!
Now I added buttons to filter my projects like:
Active Project, Type Project ...
How do I manipulate my projects object (this.projects)?
With another one this.$store.dispatch
With another getters function
I manipulate the state without changing the status?
I'm a bit confused.
Some examples of filters on lists populated in Vuex?
EDIT:
I was trying that way:
this.$store.getters.activeProjects()
But how I update this.projects?
activeProjects(){
this.projects = this.$store.getters.activeProjects()
}
does not work
I'd recommend to keep your original state intact and filter its data by using "getters".
In fact, the official documentation includes an example of how to get all the "done" todos. It might be useful for you:
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
Getters reference: https://vuex.vuejs.org/en/getters.html
Good luck!