My Vue application brings in data from Firestore using vuefire.
I import the data from a the 'lines' collection as follows:
firestore() {
return {
lines: db.collection("lines")
}
}
Each record has a Firestore generated id, which I then use a a key in a v-for loop like:
<thead>
<tr>
<th>Code</th>
<th @click="sort_string(lines,'name')"> Name</th>
<th>Quantity</th>
</tr>
</thead>
<tbody v-for="line in lines" :key="line.id">
<tr>
<td>{{line.code}}</td>
<td>{{line.name}}</td>
<td>{{line.quantity}}</td>
<button @click="pick_one(line)">+1</button>
...
In have a method pick_one
which changes the quantity on Firestore directly:
pick_one(line) {
const new_quantity = line.quantity + 1;
db
.collection("lines")
.doc(line.id)
.update({ quantity: new_quantity });
}
All of this works fine until I sort()
the underlying array ('lines').
If I sort the table and then call the function pick_one
I get a duplicate key error:
[Vue warn]: Duplicate keys detected: 'RaTIINFWTQxHQPyRmfsQ'. This may cause an update error.
I can only assume this has something to do with the way that Vuefire handles update()
calls, since the act of sorting the array does not cause this problem, only updating a line in the array while it is sorted.
My sort function (vue method) looks like this:
sort_string(table, column) {
console.log("sorting")
this.sort_toggle = -this.sort_toggle;
return table.sort((a, b) => {
if (
a[column].toString().toUpperCase() <
b[column].toString().toUpperCase()
) {
return -this.sort_toggle;
}
if (
a[column].toString().toUpperCase() >
b[column].toString().toUpperCase()
) {
return this.sort_toggle;
}
return 0;
});
},
Is there a way to avoid this behaviour?
Phil's comment provided the clue to this behaviour - in that the sort()
function is working on the underlying data rather than a copy.
I have modified my template so that the v-for now loops over a computed array which can be sorted using the (slightly modified) function.
The sort function now uses slice()
to create a copy of the underlying array:
computed: {
sorted_lines() {
return sort_string(lines, this.sort_column) // name of the column/filed to sort by
}
The sort_string function now looks like this (with addition of slice()
sort_string(table, column) {
console.log("sorting")
//this.sort_toggle = -this.sort_toggle; // moved elsewhere
return table.slice().sort((a, b) => { // slice() then sort()
if (
a[column].toString().toUpperCase() <
b[column].toString().toUpperCase()
) {
return -this.sort_toggle;
}
if (
a[column].toString().toUpperCase() >
b[column].toString().toUpperCase()
) {
return this.sort_toggle;
}
return 0;
});
},