Updating an item in an array updates all of them
I am working on an application using vuejs with vuex that uses projects, with each project having one or more tasks.
I can add, delete and update tasks. Adding and removing works fine, but updating isn't.
State in vuex dev tools:
My HTML:
<div class="job-compact row" v-for="(job, index) in project.jobs">
<div class="col-md-6">
<div class="form-group" :class="{'has-error' : errors.has('jobs.' + index + '.function')}">
<input type="text" name="jobs[function][]" class="form-control" v-model="job.function" @change="updateJobValue(index, 'function', $event.target.value)"/>
</div>
</div>
<div class="col-md-4">
<div class="form-group" :class="{'has-error' : errors.has('jobs.' + index + '.profiles')}">
<input type="number" name="jobs[profiles][]" class="form-control" v-model="job.profiles" @change="updateJobValue(index, 'profiles', $event.target.value)"/>
</div>
</div>
<div class="col-md-2">
<button v-if="index == 0" class="btn btn-success btn-sm" @click="addJob"><i class="fa fa-plus"></i></button>
<button v-if="index > 0" class="btn btn-danger btn-sm" @click="deleteJob(index);"><i class="fa fa-minus"></i></button>
</div>
</div>
As you can see, I have v-for
one that shows all my assignments. When editing a value inside my jobs, I use an event @change
to update my value. And, at the bottom, I have two buttons for adding and removing a job line.
My stores are divided into modules. The main store looks like this:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const state = {};
const getters = {};
const mutations = {};
const actions = {};
//Separate Module States
import jobCreator from './modules/job-creator/store';
export default new Vuex.Store({
modules: {
jobCreator: jobCreator
},
state,
actions,
mutations,
getters
});
The module store for this specific issue:
import store from './../../store'
const state = {
project: {
title: null,
description: null,
jobs: []
},
defaultJob: {
function: '',
title: '',
description: '',
profiles: 1,
location_id: '',
category_id: '',
budget: '',
},
};
const getters = {}
const mutations = {
addJob(state, job) {
state.project.jobs.push(job);
},
deleteJob(state, index) {
state.project.jobs.splice(index, 1);
},
updateJobValue(state, params) {
Object.assign(state.project.jobs[params.jobIndex], {
[params.field]: params.value
});
}
};
const actions = {
addJob: function (context) {
context.commit('addJob', state.defaultJob);
},
deleteJob: function (context, index) {
context.commit('deleteJob', index);
},
updateJobValue: function (context, params) {
context.commit('updateJobValue', params);
},
};
const module = {
state,
getters,
mutations,
actions
};
export default module;
The project state is mapped to the computed property of my vue instance:
computed: {
...mapState({
project: state => state.jobCreator.project,
}),
}
The problem is this: in the application image, you can see that I entered "vin" in one of the fields, but all the fields are updated.
So, all the fields function
all tasks have been updated to my last entry, not just the one that I want.
What am I doing wrong?
PS:
I have also tried the following on my mutation:
updateJobValue(state, params) {
var job = state.project.jobs[params.jobIndex];
job[params.field] = params.value;
Vue.set(state.project.jobs, params.jobIndex, job);
}
But it gives me the same result.
UPDATE: as requested, I created a jsFiddle to show my problem
source to share
The problem is in your action addJob
:
addJob: function (context) {
context.commit('addJob', state.defaultJob);
},
You reference the object state.defaultJob
every time you add a new job. This means that each element in the array state.project.jobs
refers to the same object.
You must create a copy of the object when passing in its mutation addJob
:
addJob: function (context) {
context.commit('addJob', Object.assign({}, state.defaultJob));
},
Or just pass a new object with default properties every time:
addJob: function (context) {
context.commit('addJob', {
function: '',
title: '',
description: '',
profiles: 1,
location_id: '',
category_id: '',
budget: '',
});
},
Here's a post explaining how variables are passed in Javascript: Javascript by reference or by value
source to share
I would recommend the following:
Use v-bind:value="job.function
instead v-model="job.function"
because you only want one way snapping. This code is more predictable.
Add v-key="job"
to your element v-for="(job, index) in project.jobs"
to make sure the rendering is working properly.
The first two lines should be sufficient, the object is still responsive.
var job = state.project.jobs[params.jobIndex];
job[params.field] = params.value;
Vue.set(state.project.jobs, params.jobIndex, job);
PS: In my fiddle, @change
only worked when I pressed the enter button or left the input.
source to share