I'm working on an application using vuejs with vuex which uses projects, with each project having one or more jobs.
I can add, delete and update the jobs. The adding and deleting is working perfect, but the updating is not.
The state in the 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 a v-for
that is showing all my jobs. When editing a value inside my jobs, I use the @change
event to update my value. And, at the bottom, I have two buttons to add and remove a job row.
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 problem:
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 a computed property of my vue instance:
computed: {
...mapState({
project: state => state.jobCreator.project,
}),
}
The problem is the following: In the image of the application, you can see that I entered "vin" in one of the fields, but all of the fields are updating.
So, all of the function
fields of all the jobs have been updated to my last entry, instead of only the one I want.
What am I doing wrong?
PS:
I also tried the following in my mutation function:
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's giving me the same result.
UPDATE: As requested, I created a jsFiddle to show my problem
The issue is in your addJob
action:
addJob: function (context) {
context.commit('addJob', state.defaultJob);
},
You are referencing the state.defaultJob
object each time you add a new job. That means each item in the state.project.jobs
array is referencing the same object.
You should create a copy of the object when passing it to the addJob
mutation:
addJob: function (context) {
context.commit('addJob', Object.assign({}, state.defaultJob));
},
Or, just pass in a new object with the default properties each time:
addJob: function (context) {
context.commit('addJob', {
function: '',
title: '',
description: '',
profiles: 1,
location_id: '',
category_id: '',
budget: '',
});
},
Here's a working fiddle.
Here's a post explaining how variables are passed in Javascript: Javascript by reference vs. by value
I would give the following advice:
Use v-bind:value="job.function
instead of v-model="job.function"
because you want only a one way binding. This your code more predictable.
Add a v-key="job"
to your v-for="(job, index) in project.jobs"
element just to be sure that the rendering works correctly.
The first two lines should be enought, the object is still reactive.
var job = state.project.jobs[params.jobIndex];
job[params.field] = params.value;
Vue.set(state.project.jobs, params.jobIndex, job);
PS: In my fiddle the @change
did only fire when i hit enter or left the input.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With