4

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 :(
6
  • Getters design is totally up to you I believe. About the last question - it's hard to say without actual code. Probably you didn't assign unique key attributes to v-for elements (if you mean updates are not reflected in a template). Commented Jun 27, 2017 at 14:37
  • @wostex I was trying this this morning but with no luck, to be fair, didn't do it properly as I didn't get the concept at first. Will check it tonight and let you know if that solves! Commented Jun 27, 2017 at 15:23
  • If it doesn't solve your issue - add your relevant code part to the question, it helps to debug things. Commented Jun 27, 2017 at 16:07
  • How are you updating the done flag - are you calling an action in the store? Commented Jun 27, 2017 at 16:29
  • @wostex Added the code - tried to remove all irrelevant info to make it easier to check... The funny bit is that it works fine with already existent items Commented Jun 27, 2017 at 19:17

1 Answer 1

15

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

Sign up to request clarification or add additional context in comments.

1 Comment

extra info at this issue, quite similar to mine: github.com/vuejs/vuex/issues/654

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.