2
votes

My apps structure looks something like this

App
    Column Component
        Task Component
        Task Component
        Task Component
    Column Component
        Task Component
        Task Component
    Column Component
    Column Component

enter image description here

Each column has a <draggable> component surrounding the tasks. At the App level, I have a data object with the different colums and each column with its tasks.

data: function() {
    return {
        columns: {
            new: [
                {
                    ID: 1,
                    title: 'The thing',
                    description: 'Prepare a proposal for facade'
                },
                {
                    ID: 2,
                    title: 'The thing 2',
                    description: 'Prepare a proposal for facade'
                },
                {
                    ID: 3,
                    title: 'The thing 3',
                    description: 'Prepare a proposal for facade'
                }
            ],
            inProgress: [
                {
                    ID: 4,
                    title: 'The thing 4',
                    description: 'Prepare a proposal for facade Store Opening Process (DEMO)'
                },
                {
                    ID: 5,
                    title: 'The thing 5',
                    description: 'Prepare a proposal for facade Store Opening Process (DEMO)'
                }
            ],
            done: [],
            onHold: []
        }
    }
}

I loop over the columns, and bind the object to the component:

<kool-column
    v-for="column"
    v-bind="colum"
></kool-column>

The Kool Column component:

<script id="kool-column-template" type="text/x-template">
    <div>
        <div>
            {{ column.name }} ({{ column.cards.length }})
        </div>
        <div>
            <draggable v-model="cards" group="columnkool">
                <div v-for="card in cards" :key="card.ID">
                    <kool-card v-bind="card"></kool-card>
                </div>
            </draggable>
        </div>
    </div>
</script>

<script>
    $(document).ready(function() {
        Vue.component('koolColumn', {
            template: '#kool-column-template',
            props: {
                ID: {
                    type: Number,
                    required: true
                },
                name: {
                    type: String,
                    required: true
                },
                cards: {
                    type: Array,
                    default: function() { return []; }
                }
            }
        });
    });
</script>

So, my problem, when I drag and drop the cards, I get a Vue error/warning:

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "cards"

Makes sense, I have been reading and investigating and in Vue, you want to use props to pass data down into the components, but then, to modify this data, you send an event to the parent, and let the parent modify it.

Ok, but the library I am using Vue Draggable modifies the list I pass it. So, what should I do? Solutions that I thougt about:

  1. On mounting the column component, copy the prop cards into a data of the component, use this for the list and modifications, and everytime it is changed, send to the parent the new list. I don't think this makes much sense, I will have two sources of information (the cards in the app object and the cards in my column component object), and risk that they are not aligned, after all, I am updating the list separately.
  2. When droping an item, use the library to actually "not allow" the drop, (you can use the library events and return false so it does not actually change the lists). Catch the event, send to the parent the info of what item was dragged and were the user tried to drop it and change the data accordingly. Basically, I would be using the drag and drop library just to know where the user tried to drop something, and actually changing the data manually. This also seems strange, the code is really hard to follow and there is a flicker when droping an item (because it goes back to its original position and then to where you wanted to have it).
  3. Another bad solution is to pass to the column component, not a v-bind and each property individually, but the whole object. So instead of v-bind="colum" I do :column="column" which actually makes the Vue Warning disappear and keeps the double data binding. The thing is, I am doing the anti-pattern, without the warning, but still... (btw, this approach comes from this example: Task management using Vue)

Should I go on with the approach #2?, ugly but at least not an anti-pattern and not doubling the info. Or is there another way of keeping the double data binding when I am not in "control" of the list being updated? Or maybe just a different architecture/structure for the app?.

1
Hi Joseph, get exactly same problem than yours : I want to include vue-dragable inside a child table component where I pass "rows" as a prop. I face same issue with mutability of props. Did you find an answer to your question? What did you do finally ? Looks like internet does not provide a lot of clues about the good way of dealing with vuue-draggable inside child component - SebT
Hi @SebT, im doing the solution 3 that I wrote in the post. - Joseph Pernerstorfer

1 Answers

0
votes

Vue draggable has 2 ways of handling the data provided :

  1. using v-model="array", this is immutable - if the order of items are changed, it creates a new copy.
  2. using :list="array", this uses the same reference and uses splice to update the array.

In your case, use :list="array", and you should be fine.