Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating an item in an array updates them all

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:

projects

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.

enter image description here

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.

enter image description here

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

like image 363
vincent Avatar asked Jul 01 '17 06:07

vincent


2 Answers

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

like image 136
thanksd Avatar answered Sep 22 '22 14:09

thanksd


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.

like image 24
Reiner Avatar answered Sep 23 '22 14:09

Reiner