"Maximum recursive updates exceeded" when updating an array with elements from a v-for function ref
Version
3.0.4
Reproduction link
https://github.com/AlexVipond/repro-v-for-ref-recursion
Steps to reproduce
The key step is to fully replace an array of elements while executing a function ref called by a v-for
loop. Example:
el => {
myEls.value = [...myEls.value, el] // This assignment causes an infinite reactivity loop for some reason.
}
In my reproduction repo, I matched Vue's v-for function ref docs exactly, except that I replaced the array as seen above instead of mutating array indices in place, as seen in the documentation.
Steps to reproduce using my repo (simple Vite app):
- Clone repo and enter directory
npm install
npm run dev
- Visit http://localhost:3000 and open console to see error
What is expected?
The function ref should update the list of elements, and it should stop being called after the initial render.
What is actually happening?
When you replace the array with a new array, it triggers recursion, and the app continually calls the function ref with new DOM elements. The endless loop crashes the app.
This might seem esoteric and nitpicky, but I believe it has a meaningful impact on DX for composition function authors and consumers.
I would like users of my function refs to be able to do this:
<!-- baz should receive the element and be able to correctly sort the array -->
<div
v-for="({ foo, id }) in bar"
:key="id"
:ref="baz"
>
However, given the current limitations of function refs and array editing, function ref users have to take the additional step of writing their own closure around my ref:
<!-- The developer has to wrap baz in a second closure so they can pass both the element and its index to the function ref -->
<div
v-for="({ foo, id }) in bar"
:key="id"
:ref="el => baz(el, index)"
>
For me as a composition function author, here's what the difference looks like:
// I would like to just accept the element and spread it in, preserving its sort order
const els = ref([])
const baz = el => {
els.value = [...els.value, el] // Unfortunately, this assignment triggers an infinite reactivity loop.
}
// Instead, I'm currently forced to accept and handle the index
const els = ref([])
const baz = (el, index) => {
els.value[index] = el
}
For a more complete, real-world example of how and why I use this function ref technique, see my implementation of a simple tablist in this repo: https://github.com/AlexVipond/proof-of-concept-vue-composition-api-tabs
Because the els
is a ref, and could trigger the view update when els
value changing. When view updating, all of nodes will rebuild.
Maybe just create a ref map.
const refMap = {};
Object.keys(bar).forEach(({id}) => refMap[id] = ref(null));
// in template
<div
v-for="({ foo, id }) in bar"
:key="id"
:ref="refMap[id]"
>