Home > Back-end >  null value in dynamic v-for with functional template refs
null value in dynamic v-for with functional template refs

Time:04-20

Situation

I am building a custom filtering component. This allows the user to apply n filters that are displayed with a v-for in the template. The user can update any value in the input fields or remove any of the filters afterwards.

Problem

After removing one of the filters, my array itemRefs got a null value as the last item.

Code (simplified)

<script setup>
const filtersScope = $ref([])
const itemRefs = $ref([])

function addFilter () {
  filtersScope.push({ value: '' })
}

function removeFilter (idx) {
  filtersScope.splice(idx, 1)
  itemRefs.pop() // <- necessary? has no effect
  // validate and emit stuff
  console.log(itemRefs)
  // itemRefs got at least one null item
  // itemRefs = [null]
}

// assign the values from the input fields to work with it later on
function updateValue() {
  itemRefs.forEach((input, idx) => filtersScope[idx].value = input.value)
}
</script>

<template>
  <div v-for="(filter, idx) in filtersScope" :key="filter.id">
    <input 
      type="text" 
      @keyup="updateValue" 
      :ref="(input) => { itemRefs[idx] = input }" 
      :value="filter.value"
    >
    <button @click="removeFilter(idx)" v-text="'x'" />
  </div>
  <button @click="addFilter()" v-text="'add filter  '" />
</template>

>>> Working demo

to reproduce:

  1. add two filters
  2. itemRefs got now the template refs as a reference, like: [input, input]
  3. remove one filter, itemRefs now looks: [input, null]
  4. remove the last filter, itemRefs now looks like: [null]

Question

Without the itemRefs.pop() I got the following error, after removing and applying new filters:

Uncaught TypeError: input is null

With the pop() method I prevent a console error, but the null-value in itemRefs still remains.

How do I clean my template refs cleanly?

CodePudding user response:

I don't know what's up with using $refs inside $refs but it's clearly not working as one would expect.

However, you should never need nested $refs. When mutating data, mutate the outer $refs. Use $computed to get a simplified/focused angle/slice of that data.

Here's a working example.

<script setup>
  const filtersScope = $ref([])
  const values = $computed(() => filtersScope.map(e => e.value))

  function addFilter() {
    filtersScope.push({ value: '' })
  }

  function removeFilter(idx) {
    filtersScope.splice(idx, 1);
    console.log(values)
  }
</script>
<template>
  <div v-for="(filter, idx) in filtersScope" :key="idx">
    <input type="text" 
           v-model="filtersScope[idx].value">
    <button @click="removeFilter(idx)" v-text="'x'" />
  </div>
  <button @click="addFilter()" v-text="'add filter  '" />
</template>
  • Related