Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Querying this.array.length in ref method causes recursive loop #3653

Closed
qgates opened this issue Apr 22, 2021 · 7 comments
Closed

Querying this.array.length in ref method causes recursive loop #3653

qgates opened this issue Apr 22, 2021 · 7 comments

Comments

@qgates
Copy link

qgates commented Apr 22, 2021

Version

3.0.11

Reproduction link

See here

Steps to reproduce

Self evident, causes "Uncaught (in promise) Error: Maximum recursive updates exceeded". Comment out or remove the 'console.log' line fixes the issue.

What is expected?

No error

What is actually happening?

Array length increases until maximum recursion limit, throwing 'maximum recursive update exceeded' exception.

@LinusBorg
Copy link
Member

LinusBorg commented Apr 22, 2021

This hasn't anything to do with arrays in particular. The issue is that the template ref function is run inside the render phase, during which you should not have any side-effects that , as that would trigger a re-render.

The same problem would, for example, manifest if you do this:

<template>
  <h1> {{ msg = msg + '!' }}</h1>
</template>
<script>
export default {
  data: () => ({
    msg: 'Hello World'
  })
}
</script>

Demo

Coming back to your issue, you might think that we then should just pause reactivity tracking while running a ref function. but then, you could not i.e. have a watch on that array (which might be a legitimate use case in a composable using that array for something), because adding elements to it with the template ref function would not trigger any reactive effect.

Another way to deal with this is to mark the array to be non-reactive (markRaw()), and react to its change in the update lifecycle instead.

So I would say that this is not a bug, and rather something we should document better.

@qgates
Copy link
Author

qgates commented Apr 22, 2021

Thanks @LinusBorg and please excuse any ignorance on my part. Here's what's confusing me:

  1. Everything is fine until I add the console.log into the ref function. Without it everything works fine.
  2. If I build the same demo using the Composition API, with the console.log, everything works fine too.

I gave the example using the options API because we're heavily invested in that for our app, and refactoring everything into the composition API would be a lot of work. But for the demo I gave above I can show that it works using the composition API without throwing the recursive error.

In your example it's clear what's causing the recursive render, but my biggest question is why would/should a console.log force a reactive change in the above scenario? That's why it felt like a bug to me.

Thanks! 🙂

@LinusBorg
Copy link
Member

LinusBorg commented Apr 22, 2021

Without the console.log, your template has no dependency on arr. So changing arr doesn't cause a re-render.

But when you do the console log ( which happens during the render phase), you access the arr.length, which means, the length of the array is now a dependency of your render function, and changing the arrays length will therefore cause a re-render.

In short, the console.log in the template ref function has the same effect as if you did:

<div :ref="(el) => arr.push(el) ">
  {{ arr.length }}
</div>

... you are both mutating a reactive array and read from it in the template, so to speak, leading to an endless re-render loop.

If you want feedback on the composition variation of your example, please share it.

@HcySunYang
Copy link
Member

For this use case:

<div :ref="(el) => arr.push(el) ">
  {{ arr.length }}
</div>

Because the above code does not work, I'm thinking if users just want to do this, then what is the correct way 🤔

@LinusBorg
Copy link
Member

LinusBorg commented Apr 23, 2021

use a non-reactive array, and do any effects in updated()

@mater1996
Copy link

mater1996 commented May 20, 2021

<div :ref="setItemRef" v-for="i in 3" :key="i"></div>
const itemRefs = reactive<any>([])
const setItemRef = (el: any) => {
    if (el) {
       itemRefs.push(el)
    }
    console.log(itemRefs.length)
 }

return {
   setItemRef
}

this also not work

@unbyte
Copy link
Contributor

unbyte commented May 21, 2021

The cause is that, Array.length is both modified and observed in the render function.

btw, IIRC, in vue2, ref is non-reactive.

@github-actions github-actions bot locked and limited conversation to collaborators Sep 24, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants