Navigating DOM elements without $children or polluting data

I’m working on a project with lists in which rows have editable values, which cause the order of the rows in the list to be updated after editing. The rendering works fine, rows end up in the right place.

To give Vue a hint so that it can track each node’s identity, and thus reuse and reorder existing elements, you need to provide a unique key attribute for each item

The key special attribute is primarily used as a hint for Vue’s virtual DOM algorithm to identify VNodes when diffing the new list of nodes against the old list. Without keys, Vue uses an algorithm that minimizes element movement and tries to patch/reuse elements of the same type in-place as much as possible. With keys, it will reorder elements based on the order change of keys, and elements with keys that are no longer present will always be removed/destroyed.

My problem is, that while the index on the rendered objects is correct (because it’s consistent with the id to which the index belongs) when using an unique value for the keys, the $children order of the parent (list) is not. This behaviour of $children (non-reactive) is as stated in the docs.

The direct child components of the current instance. Note there’s no order guarantee for $children , and it is not reactive. If you find yourself trying to use $children for data binding, consider using an Array and v-for to generate child components, and use the Array as the source of truth.

An obvious solution would be to add a property to the data (source of truth), to let the row know it’s selected. This ‘selected’-state however is not a property of the data, since it’s only selected in a specific list (not in other views). This kind of feels like polluting my source of truth. I don’t know another way to lookup a VNode created by a v-for directive aside from looping/filtering through the DOM nodes ($children) comparing the selected index to the index that belongs to the VNode. Filtering through the rows until the correct row is found based on a property, seems pretty resource intensive, especially since the edits will be frequent with a high number of rows.

Are a new data property to keep track of the selected row in my data or using filter() to loop through all of the rows my only options, or is there something I’m overlooking?

Im still not 100% sure what you are trying to accomplish, but if its just a matter of keeping track of which rows are selected, and you do not want to pollute your data, or build another data structure to fit/wrap your data (which a lot of people go this route) could you not just create another array, called “selectedRows” and append your data into that array when they are selected. If you need to know which rows are selected, you could do something simple as using Array.includes to see if the item you are iterating over is in your selected array. Again im not 100% sure what you are trying to accomplish with your “selected” state so I am spit balling.

Thanks! You’re pretty much on the money. This would result into a collection of selected rows, however, I can’t figure out how to connect these selected rows back to their VNodes, so I can manipulate the DOM based on the selection.

Let say I have selected a row and I need to use a button outside the row to manipulate a value inside the selected row. How would I do that, aside from iterating through the list of $children checking to see which DOM element belongs to the selected row in the array?

I was thinking maybe that I should be able to use a reference? As soon as a row becomes selected, that it would receive a reference, so I could later manipulate the VNode based on the reference. I’ve tried to make this work, but I wasn’t able to.

Edit: A small addition, aside from manipulating the row itself, how would I be able to select the row above or below the selected one based on the index? The array contains the index or id, but not a reference to the VNode. If I want to retrieve the VNode based on the index, should I be filtering to find it?

I guess I need a bit more clarrification because of your usage of $children. Are you writing your own render function? Is there a specific reason you keep wanting to look at $children of a component? This makes me think you are not looking at this problem as using data to drive your interface, since you want to use internal properties like $children on your parent component.

I’m sorry, I’m probably doing a bad case of explaining because I don’t fully understand the problem I’m facing. I’d say you’re absolutely correct that I’m struggling trying to solve it using data.

The reason I kept going back to $children, was because that to me was the only place I could find the DOM elements I was looking for.

However, I’ve managed to solve it using references. For each DOM element created, I’ve added a reference with the index. This allows me to keep track of the rows which are selected, while also enabling me to use their index to quickly select the related DOM element to for example focus() a cell/field.

<div v-for="(item, index) in list" :item="item" :ref="index">...</div>

this.$refs[index][0] - The key is added because the ref-attribute was created in a v-for directive.

Thanks for helping me think from different perspectives about the problem.

I feel like you are going about this the wrong way within Vue, if you are thinking “DOM”. Your data and events should drive your interface, not the requirement of needing direct access to DOM elements. It’s a shift in thinking from traditional JS for sure.

Yes, I couldn’t agree more, but I found this in the documentation and is basically exactly what I needed:

Despite the existence of props and events, sometimes you might still need to directly access a child component in JavaScript. To achieve this you can assign a reference ID to the child component using the ref attribute.

An example case very similar to mine:

methods: {
  // Used to focus the input from the parent
  focus: function () {
    this.$refs.input.focus()
  }
}