Iterating over an array of objects which I desperately want to be reactive in Vue.js

[a note to my future self from my current self as of 17 April 2018: are you here again? Is there definitely a unique key on that loop? Are you sure? GO PUT ONE ON NOW. Use the uuid package, it’s easy. Then come back if it still doesn’t work.]

This took me a while yesterday to figure out. There were a couple of issues I had: how to keep the individual items reactive (spoiler: easy once I stopped being lazy) and how to make an array of objects reactive.

I’m making a list of items, each of which is editable and which can be sorted. It looks like this:

Screen Shot 2018-04-10 at 08.18.31
Edit each name individually
Screen Shot 2018-04-10 at 08.18.05
Order the items (not draggable, I’d rather press buttons)

At first I had one component which handled everything but had difficulty making the iterated objects reactive so that I could edit the name and have the item update.

I am getting them from a database query made on created () but don’t know how many are in the array and looping through and using Vue.set on each object and property seemed a bit extreme and wrong. It was. But easily sorted! I ended up passing each item to a separate component as outlined here and whooo, it works. I have my list in which each item’s name can be edited.

Screen Shot 2018-04-10 at 08.52.54

What about sorting? This had me flummoxed for a while. I couldn’t get it so that only the two items which are being switched are updated. (When a button is pressed, one item and either the one above or one below — depending on which button has been pressed — swap places.)

For one, I wasn’t thinking of it correctly. What I want, it seems obvious now but wasn’t then, is I need to swap the array elements. Eg items[ i ] = items[ i – 1] and vice versa. Ok, so that’s straightforward.

BUT the list doesn’t update! The array elements / list items aren’t reactive in this context. The swap is handled by the parent component of the individual item component (<list-component> in the image above) and the objects which make up the array of items aren’t reactive here. (I am glossing over how the parent component knows of a button press in the child component — it emits an event which the parent component is listening for.)

First, it helps me to think of what I want to do. On button click I need to:

  1. Update the order of both items in the database
  2. If the order has been updated, swap the array elements.

The first is straightforward (WP REST API ftw 🙂 ) and I chained the requests to swap the elements if they were successful.

Next I need to make the two array elements reactive to swap them (I am using the response data, ie the updated object, in the swap) and have the list update. Once I got it, it was kind of obvious (it’s one of those problems):

Screen Shot 2018-04-10 at 12.21.32

One tricky part was that I couldn’t understand why self.items.$set({ ... }) wasn’t working. I had read the “Common Beginner Gotchas” (I’m a common beginner, definitely) and thought I could. self.items is an array.

For whatever reason (again, I’ve been using vue for about 3 weeks here), the $set method wasn’t available to the array in that context, so I had to use the method on this / self. Which, I have to admit, is explained in “Change Detection Caveats” here.

So done and dusted, right?

Almost. Initially the key on <item-component> was item.ID. Which is, of course, unique when I was just iterating over the initial array. But once I went to swap items, there was a millisecond or so in which two array elements had the same item.ID — more than enough time for Vue to throw up its hands and complain about duplicate keys.

I decided to use the uuid npm package which solved the problem. The keys now stay unique.

And that was it! phew.


Leave a Reply

Your email address will not be published. Required fields are marked *

By submitting this comment, you are agreeing to the use of Akismet which helps reduce spam. You can view Akismet’s privacy policy here. Your email, website and name are also stored on this site.