Skip to content

$watch can register it's effects on the wrong instance #2381

@tim-janik

Description

@tim-janik

Version

3.0.0

Steps to reproduce

I'm in the process of porting a smallish front-end (10klocs) from Vue2 to Vue-3.0.0.
I have one mixin that installs a watch and uninstalls it like this (abbreviated):

  updated: function () {
      this.$dom_updates.unwatch1?.();
      this.$dom_updates.unwatch1 = null;
      const dom_update_reactive = vm => { ... };
      this.$dom_updates.unwatch1 = this.$watch (dom_update_reactive, this.$forceUpdate);
  },
  beforeUnmount: function () {
    this.$dom_updates.unwatch1?.();
    this.$dom_updates.unwatch1 = null;
  },

Calling unwatch1 always triggers the following:

vue.js:1180 [Vue warn]: Unhandled error during execution of beforeUnmount hook 
  at <BModaldialog key=1 value=false onInput=fn<onInput>  ... > 
  at <BApp>
vue.js:1180 [Vue warn]: Unhandled error during execution of scheduler flush. 
  This is likely a Vue internals bug. Please open an issue at 
  https://new-issue.vuejs.org/?repo=vuejs/vue-next 
  at <BApp>
vue.js:212 Uncaught (in promise) TypeError: Cannot read property 'indexOf' of null
    at remove (vue.js:212)
    at Object.unwatch1 (vue.js:6489)
    at Proxy.beforeUnmount (util.js:737)
    at callWithErrorHandling (vue.js:1296)
    at callWithAsyncErrorHandling (vue.js:1305)
    at Array.hook.__weh.hook.__weh (vue.js:3665)
    at invokeArrayFns (vue.js:275)
    at unmountComponent (vue.js:6157)
    at unmount (vue.js:6071)
    at patchKeyedChildren (vue.js:5892)
remove	@	vue.js:212
(anonymous)	@	vue.js:6489
beforeUnmount	@	util.js:737
callWithErrorHandling	@	vue.js:1296

It looks like Vue's unwatch callback as returned from $watch() cannot remove itself from instance.effects, since vue.js reads:

vue.js:211:const remove = (arr, el) => {
vue.js:212:    const i = arr.indexOf(el);
  ...
vue.js:6489:             remove(instance.effects, runner);`

Since I'm in the middle of a transition, it's impossible for me to deduce a minimal test case.
I hope the trace is useful regardless.

The full (unported) Vue2 based code can be found here, still using beforeDestroy instead of beforeUnmount:

https://github.com/tim-janik/beast/blob/7e743157617f74f842aabbe6efbff2184b0b1109/ebeast/util.js#L696

What is expected?

unwatch() as returned from $watch() can be called without exception.

What is actually happening?

An exception is thrown.

Metadata

Metadata

Assignees

No one assigned

    Labels

    🐞 bugSomething isn't workinghas PRA pull request has already been submitted to solve the issuescope: reactivity

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions