Subscribe on changes!

"Maximum recursive updates exceeded" when updating an array with elements from a v-for function ref

avatar
Dec 8th 2020

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):

  1. Clone repo and enter directory
  2. npm install
  3. npm run dev
  4. 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

avatar
Dec 9th 2020

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]"
>
avatar
Dec 9th 2020

Thanks, that's definitely a viable solution. I think for now I'll just use el => els.value.push(el) plus an onBeforeUpdate hook to reset els.value to an empty array as shown in the Vue docs.

I'll close this issue since both of these proposed solutions work well.