vuejs / vue

This is the repo for Vue 2. For Vue 3, go to https://github.com/vuejs/core
http://v2.vuejs.org
MIT License
207.98k stars 33.68k forks source link

[Vue warn]: Error in nextTick: "RangeError: Maximum call stack size exceeded" #9081

Closed Rolanddoda closed 5 years ago

Rolanddoda commented 5 years ago

Version

2.5.17

Reproduction link

https://jsfiddle.net/chrisvfritz/50wL7mdz/

Steps to reproduce

The error is at that function of vue.

/**
 * Helper that recursively merges two data objects together.
 */
function mergeData (to, from) {
  if (!from) { return to }
  var key, toVal, fromVal;
  var keys = Object.keys(from);
  for (var i = 0; i < keys.length; i  ) {
    key = keys[i];
    toVal = to[key];
    fromVal = from[key];
    if (!hasOwn(to, key)) {
      set(to, key, fromVal);
    } else if (isPlainObject(toVal) && isPlainObject(fromVal)) {
      mergeData(toVal, fromVal);
    }
  }

What is expected?

It is kinda weird and I don't know from where it comes from or why is happening. So I can't provide a link on how to reproduce it.

What is actually happening?

Many times the app is working perfectly but few times, this error appears. And the app is broken.


Sorry for not providing a Link to minimal reproduction but I don't even know from where this error comes from since it is not happening every time.

For more info the error is showing by using those components:

1.

<template>
    <div class="tasks-content">
        <v-tabs class="tabs" grow hide-slider height="60px" v-model="active_tab">
            <v-tab
                    centered
                    v-for="tab in tabs"
                    :key="tab.name"
                    :href="`#${tab.name}`"
                    v-model="active_tab"
            >
                {{ tab.name }}
            </v-tab>
        </v-tabs>

        <v-tabs-items v-model="active_tab">
            <v-tab-item v-for="tab of tabs" :key="tab.id" :value="tab.name">
                <v-card flat>
                    <!--<component :tasks="tasks" :is="active_tab"></component>-->
                </v-card>
            </v-tab-item>
        </v-tabs-items>
    </div>
</template>

<script>
    import MyTasks from './MyTasks.vue'
    import AllTasks from './AllTasks.vue'

    export default {
        name: 'TasksContent',
        components: {
            MyTasks, AllTasks
        },

        props: {
            tasks: Array
        },

        data: () => ({
            active_tab: 'MyTasks',
            tabs: [
                { id: 1, name: 'MyTasks' },
                { id: 2, name: 'AllTasks' },
            ]
        }),

    }
</script>

<style lang="scss" scoped>
    .tasks-content {

    }
</style>

2.

<template>
    <div class="my-tasks">
        <div class="chips">
            <v-chip small color="orange" text-color="white">
                <v-avatar class="teal">14</v-avatar>
                All
            </v-chip>

            <v-chip small color="orange" text-color="white">
                <v-avatar class="teal">9</v-avatar>
                Completed
            </v-chip>

            <v-chip small color="orange" text-color="white">
                <v-avatar class="teal">3</v-avatar>
                Pending
            </v-chip>

            <v-chip small color="orange" text-color="white">
                <v-avatar class="teal">2</v-avatar>
                Behind
            </v-chip>
        </div>
        <div class="custom-table">
            <div class="header">
                <div class="col">Assignee</div>
                <div class="col">Project</div>
                <div class="col">Status</div>
            </div>
            <div class="body">
                <div class="row" v-for="task in tasks" :key="tasks.id">
                    <div class="assignee-col">
                        <v-img :src="require('@/assets/temp/user.png')" height="40" width="40" />
                    </div>
                    <div class="project-col">
                        {{ task.description.length > 25 ? task.description.slice(0, 40) : task.description }}
                    </div>
                    <div class="status-col">
                        {{task.status}}
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'MyTasks',
        props: {
            tasks: {
                type: Array,
                default: () => []
            }
        }
    }
</script>

<style lang="scss" scoped>
    .my-tasks {
        .chips {
            padding: 20px 0;
        }
        .custom-table {
            .header {
                display: grid;
                grid-template-columns: 1fr 3fr 1fr;
                .col {
                    padding: 10px;
                }
            }
            .body {
                .row {
                    display: grid;
                    grid-template-columns: 1fr 3fr 1fr;
                    border-bottom: 1px solid gray;
                    grid-gap: 5px;
                    padding: 10px;
                }
            }
        }
    }
</style>
posva commented 5 years ago

Unfortunately this is too much for us. Come back if you manage to boil it down to a minimal repro :)

Lyncis commented 5 years ago

I got the same issue with a completely empty page... Have no idea what can cause this

Rolanddoda commented 5 years ago

Yeah it is kinda difficult to find why this error happening. FYI I am still getting this error sometimes.

dracon commented 5 years ago

I figured out if you have a view component with a name "ComponentName" and imported a component with the same name attribute this will give you this error. Just remove the name attribute in the view component :)

carlmathisen commented 5 years ago

Thank you @dracon! This error left me scratching my head for a while.

xerosanyam commented 5 years ago

This solution didn't work for me. :-/

emahuni commented 4 years ago

@dracon thank you so much, to add to your answer:

I figured out if you have a view component with a name "ComponentName" and imported a component with the same name attribute this will give you this error. Just remove the name attribute in the view component :)

By that he meant nested components. So I guess the Vue team need to look at it from that perspective. @posva Please reopen this issue, it's real and there is reproduction of this. Coz he hit the bulls-eye with my setup. I have vue@2.6.10.

This is happening when you just have nested components eg: ComponentName->SomeOther->ComponentName

For example, I have a v-if directive in the component ComponentName that prohibits rendering of the inner SomeOther component, the thing works smoothly all the time as long as the condition isn't met to render SomeOther. But if the v-if evaluates to expose and render the inner component SomeOther that in turn reintroduces the initial outer component ComponentName, the error is thrown when some functions or in my case event handlers for SIP sessions are called in the nested component.

Here is a stack trace up to the line that is in the stack trace before being swallowed by Vue:

 [Vue warn]: Error in nextTick: "RangeError: Maximum call stack size exceeded"
  | window.console.error | @ | console.js:223
  | warn | @ | vue.runtime.esm.js?2b0e:619
  | logError | @ | vue.runtime.esm.js?2b0e:1884
  | globalHandleError | @ | vue.runtime.esm.js?2b0e:1879
  | handleError | @ | vue.runtime.esm.js?2b0e:1839
  | eval | @ | vue.runtime.esm.js?2b0e:1982
  | flushCallbacks | @ | vue.runtime.esm.js?2b0e:1906
  | Promise.then (async) |   |  
  | timerFunc | @ | vue.runtime.esm.js?2b0e:1933
  | nextTick | @ | vue.runtime.esm.js?2b0e:1990
  | queueWatcher | @ | vue.runtime.esm.js?2b0e:4396
  | update | @ | vue.runtime.esm.js?2b0e:4538
  | notify | @ | vue.runtime.esm.js?2b0e:730
  | reactiveSetter | @ | vue.runtime.esm.js?2b0e:1055
  | eval | @ | sip-js.mixin.js?9171:433

For example here are the lines surrounding 433 in my code:

  this.$nextTick(() => session.data.call.durationTimer = setInterval(() => {
        session.data.call.duration++; // this line is 433 in my sip-js.mixin.js file (see above trace)
    }, 1000););

The event handlers themselves are not to blame, it's this nesting that's causing this. There is a recursion that is probably causing an infinite number of this component tree to be recreated over and over and over and over again or the events are being fired/handled over over and over, causing the call stack to overflow.

My solution was to actually rename the component at import and inject it in the created hook. I used a Vue.component('ComponentNameNested', ComponentNameNested) and then <component-name-nested /> in the template. and changed the-above code to this:

    session.data.call.durationTimer = setInterval(() => {
        session.data.call.duration++;  // this line is 433 in my sip-js.mixin.js file (see above trace)
    }, 1000);

because the error was still happening even if I fixed using the solution, or if I do that and don't do the solution. So this has something to do with nextTick (as the trace suggests), and nesting.

Here is reproducible code I guess: https://stackoverflow.com/questions/44520195/vue-js-maximum-call-stack-size-exceeded-error-passing-data-from-parent-to-chi

posva commented 4 years ago

@emahuni The case described by @dracon is working as expected: you cannot use a component with the same name as the current .vue component file because A Vue component implicitly registers itself with its given name to allow recursive components

emahuni commented 4 years ago

@posva I have realised this has nothing to do with recursive components at all per se, you just see it because of related reasons. I am now getting that error on a different place. The issue is with Vue's Next Tick function and _traverse function. As the application is growing and you use events to communicate between components you end up with these errors because Vue is calling functions in a loop at the same time and blotting the call stack. I am working on a SIP app and it uses events to kick in an incoming call and update components. This happens when a call comes in. It means there are events that roll out certain functions that are not heavy at all.

[Vue warn]: Error in nextTick: "RangeError: Maximum call stack size exceeded"
 RangeError: Maximum call stack size exceeded
    at Function.keys (<anonymous>)
    at _traverse (webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:2120)
    at _traverse (webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:2118)
    at _traverse (webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:2122)
    at _traverse (webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:2118)
    at _traverse (webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:2122)
    at _traverse (webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:2118)
    at _traverse (webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:2122)
    at _traverse (webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:2118)
    at _traverse (webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:2122)

that points to this:


function _traverse (val, seen) {
  var i, keys;
  var isA = Array.isArray(val);
  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
    return
  }
  if (val.__ob__) {
    var depId = val.__ob__.dep.id;
    if (seen.has(depId)) {
      return
    }
    seen.add(depId);
  }
  if (isA) {
    i = val.length;
    while (i--) { _traverse(val[i], seen); } // 👈🏽here it's recursing calls, which cause these call stack errors
  } else {
    keys = Object.keys(val); // this is line 2120 in the file and trace so the next code is 👇🏽👇🏽
    i = keys.length;
    while (i--) { _traverse(val[keys[i]], seen); } // 👈🏽here also ...
  }
}

https://www.hhutzler.de/blog/avoid-maximum-call-stack-size-exceeded-in-javascript/

pay attention to this:

Overview Error Maximum call stack size exceeded is raised when calling a recursive function in a loop This is expected – as every Javascript Engine has a maximum Call Stack Range

Vue is doing exactly that and it's causing these errors. So all these fixes are more of workarounds that just reduce the amount of functions that get called or delay something causing it to just work.

I am using Chrome latest build, 79.0.3945.130 (Official Build) (64-bit) Please don't take the workaround as the solution, there is a bug here that needs attention, of which I hope this won't spill over into Vue 3.

posva commented 4 years ago

I appreciate your concern, I do know what the error means and it can be due to many reasons related to a wrong usage like a computed property depending on itself.

If you believe this is a Vue bug, please create a boiled down reproduction and open a new issue as indicated at https://new-issue.vuejs.org/?repo=vuejs/vue

emahuni commented 4 years ago

ok I edited the Vue.runtime.esm.js file as follows and it works perfectly:

function _traverse (val, seen) {
  var i, keys;
  var isA = Array.isArray(val);
  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
    return
  }
  if (val.__ob__) {
    var depId = val.__ob__.dep.id;
    if (seen.has(depId)) {
      return
    }
    seen.add(depId);
  }
  if (isA) {
    i = val.length;
    while (i--) { setTimeout(_traverse, 0, val[i], seen); } 
  } else {
    keys = Object.keys(val); 
    i = keys.length;
    while (i--) { setTimeout(_traverse, 0, val[keys[i]], seen); } 
  }
}

so this needs to be done where this kind of code appears all-over Vue

emahuni commented 4 years ago

Yes it's a Vue bug and the above is the solution to this bug. But I can see this kind of architecture throughout Vue, cause the first trace has nothing to do with _traverse (maybe it does, I just didn't go thru it the way I did this time, but I could see there is a bug at that time)... The point is, if for whatever reason the developer does something that causes Vue to loop those callbacks, it should do it gracefully such that it doesn't cause the crash

emahuni commented 4 years ago

I appreciate your concern, I do know what the error means and it can be due to many reasons related to a wrong usage like a computed property depending on itself.

If you believe this is a Vue bug, please create a boiled down reproduction and open a new issue as indicated at https://new-issue.vuejs.org/?repo=vuejs/vue

Ok, let me do that as soon as I can, I am under production hell right now

LinusBorg commented 4 years ago

This fix of yours is unfortunately not a viable solution (at least in its current form, probably in general though), as this will break a lot of hings that depend on reactivity change detection being synchronous.

emahuni commented 4 years ago

@LinusBorg ok, I was kinda worried about it actually when trying it, now that you also mention it, you have just asserted it. A better solution should then be worked out from this, at least the bug has a visible trail from what I see. I need to figure out how to reproduce this as @posva said.

emahuni commented 4 years ago

@LinusBorg reactivity seem to be working, my worry was on that

setTimeout() function always creates a Top Level Function

So I was worried it may isolate the data associated with the cbs, but it is working. Let me first cleanup code I had modified trying to solve this issue in my code and make sure things work as intended and report back.

emahuni commented 4 years ago

I can confirm that everything is working as expected except that it is now a bit slow because of that timeout i guess. So a better approach to this is needed. But that is in itself a bug and a solution is needed to make sure Vue doesn't overflow the call stack when it traverses those callbacks.

emahuni commented 4 years ago

I can confirm that everything is working as expected except that it is now a bit slow because of that timeout i guess. So a better approach to this is needed. But that is in itself a bug and a solution is needed to make sure Vue doesn't overflow the call stack when it traverses those callbacks.

@LinusBorg and @posva - I take the slowness part back, it was some other change I had done that was causing that. It's running well with that fix. No problems with reactivity whatsoever, maybe what needs to be done is actually just test the modification using proper Vue tests in CI after a PR. This is an actual solution to this bug.

LinusBorg commented 4 years ago

This is an actual solution to this bug.

Before sending a PR, make sure you can re-create "this bug" in a runnable reproduction that you post as a new issue.

emahuni commented 4 years ago

This fix of yours is unfortunately not a viable solution (at least in its current form, probably in general though), as this will break a lot of hings that depend on reactivity change detection being synchronous.

The synchronous part needs to be tested as you say to fully proof this. I don't know which parts depend on this sync behaviour of reactivity that are dormant in my tests 🤷🏽‍♂️. I am aware that setTimeout does cause the code to be async, but it's just for 0 ms and in order it was iterated in that loop. I am not sure if that causes any breakage and at what point coz it didn't break when I tried it. The tests are end to end, of a large app and running on Quasar too. So if there was any reactivity breakage I could have noticed it right away.

emahuni commented 4 years ago

This is an actual solution to this bug.

Before sending a PR, make sure you can re-create "this bug" in a runnable reproduction that you post as a new issue.

yes, noted, thanks. Will do it once I exit production hell.

LinusBorg commented 4 years ago

I am aware that setTimeout does cause the code to be async, but it's just for 0 ms and in order it was iterated in that loop.

It's not about how long it is. it's about the fact that it's async at all.

The result is that e.g. during the patch phase of rendering, nested dependencies may not be collected. on immedita deep watchers, nested dependencies may not be collected and therefore changes will falsly not trigger the watcher, etc pp.

Dependency collection is written as an async process. putting a setTimeout in there will have unforseen consequences in all sorts of cases.

emahuni commented 4 years ago

Oh my God! I updated things and had not done a fork and I ran into this problem again, had to come back here. If I had not written this issue I was going to bang my head... Let me fork and PR right now.

LinusBorg commented 4 years ago

No, don't send a PR just yet.

As previously discussed: First, create a new issue with a propert reproduction that actually demonstrates the problem. We still haven't gotten to that stage yet.

Once we are there, there's still the point that in my opinion, traversal can't be async.

emahuni commented 4 years ago

My problem right now is how to reproduce this in a simpler way, so that I can create a new issue and a PR for it. I can clearly see that this has something to do with events as my app is using them a lot. And as they get many this problem pops up. If anyone can please throw a reproducible link I can use. The above solution fixes it and the app works without any problems. It's a Quasar stack so if there was anything wrong in the fundamental way Vue works, it should have broken down somehow in a visible and testable way, all tests run well after this fix. I don't want to forget doing this again like last time.

emahuni commented 4 years ago

No, don't send a PR just yet.

As previously discussed: First, create a new issue with a propert reproduction that actually demonstrates the problem. We still haven't gotten to that stage yet.

Once we are there, there's still the point that in my opinion, traversal can't be async.

Yes I understand that. That's what I was just contemplating.

emahuni commented 4 years ago

Hi. Yesterday I ran tests on a vue fork with that modification, it didn't go well. There were about 4 fails around watchers etc. Yes, though things work in the production code, there is something it messes up in Vue and it means it fails something somewhere in how Vue works. As you said travesal can't be async, I think that is the reason. Just need to find a way to make it sync and still solve that problem I guess.

So I ended up changing my implementation instead (had to rethink things). Will post an issue once I figure out how to reproduce this (specifically what it is that I had done that was causing this).

farfromrefug commented 4 years ago

I think i faced that issue today while using deep watch on objects containing references to Vue objects. Changing the _traverse method to this (added the check for val._isVue)

function _traverse (val, seen) {
  var i, keys;
  var isA = Array.isArray(val);
  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode || val._isVue) {
    return
  }
  if (val.__ob__) {
    var depId = val.__ob__.dep.id;
    if (seen.has(depId)) {
      return
    }
    seen.add(depId);
  }
  if (isA) {
    i = val.length;
    while (i--) { setTimeout(_traverse, 0, val[i], seen); } 
  } else {
    keys = Object.keys(val); 
    i = keys.length;
    while (i--) { setTimeout(_traverse, 0, val[keys[i]], seen); } 
  }
}

Fixed it for me. Might it has something to do with the val instanceof VNode in esm?

LinusBorg commented 4 years ago

objects containing references to Vue objects

That's simply something you should not do. Vue objects / components contain a gazillion references to other objects, including curcular references, DOM nodes etc

We can think about adding this check though. Please open a new issue for that.

farfromrefug commented 4 years ago

@LinusBorg Indeed should not happen! Happened of the context i am working in. I am using Vue with Nativescript. I wont get into details on why it was the case but i fixed it on my side by making the property where i need to store that reference to non enumerable. That way _traverse wont pick it! Now it still fill that that _traverse method is missing something to avoid circular references. You cant expect all devs to assure this will never happen.

ganeshkumarpolipalli-accolite commented 3 years ago

@farfromrefug how to override this _traverse method without updating the Vue.runtime.esm.js file?

SanjayBhan commented 3 years ago

After modification, offline code works but what if I need updated CDN or distributed code update, any help

SanjayBhan commented 3 years ago

ok I edited the Vue.runtime.esm.js file as follows and it works perfectly:

function _traverse (val, seen) {
  var i, keys;
  var isA = Array.isArray(val);
  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
    return
  }
  if (val.__ob__) {
    var depId = val.__ob__.dep.id;
    if (seen.has(depId)) {
      return
    }
    seen.add(depId);
  }
  if (isA) {
    i = val.length;
    while (i--) { setTimeout(_traverse, 0, val[i], seen); } 
  } else {
    keys = Object.keys(val); 
    i = keys.length;
    while (i--) { setTimeout(_traverse, 0, val[keys[i]], seen); } 
  }
}

so this needs to be done where this kind of code appears all-over Vue

After modification, offline code works but what if I need updated CDN or distributed code update, any help

iamgabrielsoft commented 2 years ago

2022 and @dracon answer works well

kbower commented 2 years ago

Has anyone else had problems with this suddenly beginning very recently? (End of Oct 2022)

blizzardKv commented 1 year ago

Has anyone else had problems with this suddenly beginning very recently? (End of Oct 2022)

yes, reproduced with vuetify recently, with vue 2.7.13 and vuetify 2.3.21

kbower commented 1 year ago

In our case, it turned out to be a third-party (MapQuest) software change.

blizzardKv commented 1 year ago

In our case, it turned out to be a third-party (MapQuest) software change.

I had literally copied name of main component and child component, my mistake 🤦

Ninili commented 1 year ago

This exact error just happened to me after updating vueUse from v.9 to v.10. in a vueJs 2.6. project.

soylomass commented 8 months ago

I think i faced that issue today while using deep watch on objects containing references to Vue objects. Changing the _traverse method to this (added the check for val._isVue)

function _traverse (val, seen) {
  var i, keys;
  var isA = Array.isArray(val);
  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode || val._isVue) {
    return
  }
  if (val.__ob__) {
    var depId = val.__ob__.dep.id;
    if (seen.has(depId)) {
      return
    }
    seen.add(depId);
  }
  if (isA) {
    i = val.length;
    while (i--) { setTimeout(_traverse, 0, val[i], seen); } 
  } else {
    keys = Object.keys(val); 
    i = keys.length;
    while (i--) { setTimeout(_traverse, 0, val[keys[i]], seen); } 
  }
}

Fixed it for me. Might it has something to do with the val instanceof VNode in esm?

You saved my life. The worst part is that this evil bug only happens in production.