Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lists and Components not updating after data change - (VueJS + VueX)

Tags:

vue.js

vuex

A question about best practice (or even a go-to practice)

I have a list (ex. To-do list). My actual approach is:

  • On my parent component, I populate my 'store.todos' array. Using a getter, I get all the To-do's and iterate on a list using a v-for loop.

  • Every item is a Component, and I send the to-do item as a prop.

  • Inside this component, I have logic to update the "done" flag. And this element display a checkbox based on the "state" of the flag. When it does that, it do an action to the db and updates the store state.

Should I instead:

  • Have each list-item to have a getter, and only send the ID down the child-component?

Everything works fine, but if I add a new item to the to-do list, this item is not updated when I mark it as completed. I wonder if this issue is because I use a prop and not a getter inside the child component

Code:

store:

const state = {     
    tasks: []
}

const mutations = {     
CLEAR_TASKS (state) {
    state.tasks = [];
},
SET_TASKS (state, tasks) {
    state.tasks = tasks;
},
ADD_TASK (state, payload) {
    // if the payload has an index, it replaces that object, if not, pushes a new task to the array
    if(payload.index){          
        state.currentSpaceTasks[payload.index] = payload.task;
        // (1) Without this two lines, the item doesn't update
        state.tasks.push('');
        state.tasks.pop();
    }
    else{           
        state.tasks.push(payload.task);
    }
},
SET_TASK_COMPLETION (state, task){

    let index = state.tasks.findIndex(obj => obj.id == task.id);
    state.tasks[index].completed_at = task.completed_at;
}
}

const getters = {   
(...)
getTasks: (state) => (parentId) => {        
    if (parentId) {
        return state.tasks.filter(task => task.parent_id == parentId );
    } else {            
        return state.tasks.filter(task => !task.parent_id );
    }
}
(...)
}

const actions = {   
(...)
/*
* Add a new Task 
* 1st commit add a Temp Task, second updates the first one with real information (Optimistic UI - or a wannabe version of it)
*/
addTask({ commit, state }, task ) {         
        commit('ADD_TASK',{
            task
        });
    let iNewTask = state.currentSpaceTasks.length - 1;

axios.post('/spaces/'+state.route.params.spaceId+'/tasks',task).then(
        response => {
            let newTask = response.data;    
            commit('ADD_TASK',{
                task: newTask,
                index: iNewTask
            });
        },
        error => {
            alert(error.response.data);
        });

},
markTaskCompleted({ commit, dispatch, state }, task ){

        console.log(task.completed_at);
        commit('SET_TASK_COMPLETION', task);
        dispatch('updateTask', { id: task.id, field: 'completed', value: task.completed_at } ).then(
            response => {
                commit('SET_TASK_COMPLETION', response.data);
            },
            error => {
                task.completed_at = !task.completed_at;
                commit('SET_TASK_COMPLETION', task);
            });

},
updateTask({ commit, state }, data ) {              
    return new Promise((resolve, reject) => {       
        axios.patch('/spaces/'+state.route.params.spaceId+'/tasks/'+ data.id, data).then(
        response => {
            resolve(response.data);
        },
        error => {
            reject(error);      
        });
    })
}

}

And basically this is my Parent and Child Components:

Task List component (it loads the tasks from the Getters) (...)

    <task :task = 'item' v-for = "(item, index) in tasks(parentId)" :key = 'item.id"></task>            
(...)

The task component display a "checkbox"(using Fontawesome). And changes between checked/unchecked depending on the completed_at being set/true.

This procedure works fine:

  1. Access Task list
  2. Mark one existing item as done - checkbox is checked

This procedure fails

  1. Add a new task (It fires the add task, which firstly adds a 'temporary' item, and after the return of the ajax, updates it with real information (id, etc..). While it doesn't have the id, the task displays a loading instead of the checkbox, and after it updates it shows the checkbox - this works!
  2. Check the newly added task - it does send the request, it updates the item and DB. But checkbox is not updated :(
like image 310
Victor Mikó Avatar asked Oct 23 '25 09:10

Victor Mikó


1 Answers

After digging between Vue.js docs I could fix it.

Vue.js and Vuex does not extend reactivity to properties that were not on the original object.

To add new items in an array for example, you have to do this:

// Vue.set
Vue.set(example1.items, indexOfItem, newValue)

More info here: https://v2.vuejs.org/v2/guide/reactivity.html

and here: https://v2.vuejs.org/v2/guide/list.html#Caveats

At first it only solved part of the issue. I do not need the "hack" used after pushing an item into the array (push and pop an empty object to force the list to reload)

But having this in mind now, I checked the object returned by the server, and although on the getTasks, the list has all the fields, including the completed_at, after saving a new item, it was only returning the fields that were set (completed_at is null when created). That means that Vue.js was not tracking this property.

I added the property to be returned by the server side (Laravel, btw), and now everything works fine!

If anybody has a point about my code other than this, feel free to add :)

Thanks guys

like image 91
Victor Mikó Avatar answered Oct 25 '25 23:10

Victor Mikó



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!