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:
- add two filters
itemRefs
got now the template refs as a reference, like:[input, input]
- remove one filter,
itemRefs
now looks:[input, null]
- 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>