vuejs / core

🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
https://vuejs.org/
MIT License
46.95k stars 8.24k forks source link

mount() during flushJobs() causes computed property to return null #9358

Open bdiz opened 12 months ago

bdiz commented 12 months ago

Vue version

3.2.47

Link to minimal reproduction

https://play.vuejs.org/#__DEV__eNq9WFtv2zYU/iucsIcEiaklKTAgs712iTt06A1t15eqQBSJjpVKpEZSucD1f99HUqSkRE6zPOzFNslz+c6F5xx6Hb2oa3rVsOg4mqpMFrWeJ7yoaiE1WZNMslQzkJANWUpRkSQCbRL9FmheFiV7XSjtj2nsd4xUS5nwZcMzXQjeyvubc5YxpVJZlLdvRMM1y08E5HHG9Y4UQofVPjHL91LUarbe7JJ1wgmJY/JpVShSS3YFGkX0ikF1YCI4qwqlCn5BNKvqEjqJkEQynjNJPJokIp8bRq5TyUFJjeSBbtrSf/8+Izu7ZDYnvClLGERIJjhMTuGXWeejrchpbT5PU53uWnd4/msJCVAwI7nImsqodMIWJTOrnSTKi6skMlzEU1Olb0tG80LBrlvwJhGHQutpL7jAR8ozhlPw0Mq4eKfld8KCwnOR31JzwPOTVVHmfTJD6EVRLT5qaTzauSOJvojzS5Zp8s5+fe2BEMBYiguYcNR66IP1Zom4Bx8506zjmW4kD9qwuzEHzpxK5Kw8ZUuYY+Ofw5MGgl0E3nZFCOPpecmOyTItFXObG4sLIs0PL3aJRD3oibVROvZy6hTh12FJiL6tIdRZuu83JfunKSTLj4mWSHenzOqyJM7yvAOrkbeUp5UJTRJZBFTJrHVcywT3NOAKugFXm7TuTPaBBqmJIoSd4VL8vA7iN55JJQk/s8I7roJnZZMzBTbL4EylBs2hpyTkd4LgnbW0AEv27lM7U/ZwCjWIZsd9bPacVZ3iKi34SWsMINu4UhcurFv4/d3NAH0baG/0XmfIXl90S++cSVz2DHzn5TyiGPnU2yeoPl7wllw67OXSvcCPRP7w/4z80HcPWLJiKa6pKeL/wRzH9DR7WmTbzDBW+NQzXnuLQ3UnxPYePTXEvgwgxK4EmEINxKECGNXof138e1oPx7QGrAeDZO5frccCMzn1Y2D9axd6VAc0Lcs/GWcS7IBhQvvGwFL30qor2gdbmXw76tn7hVJqbb4YY/h6B80o0QNQDsc5xnB0fjBwQpFus9VGZGwzFL2vZqmZ3EHiz8lyLOA2/bYGfRQprdLaBtMKxbdN8F16KQoO+1zlHL2R7MbOWDlbpk2JeczfKZsfXavyE1d38Z7cIcevbVcTnnrJBlXlfjG1aiumVyLvzBL8pCyybwOV1sudsJFbhnr004+pRtw9jcP8i4WfG7EiZGoETMrWxbhluBwTs6dmvvZZPdvvTMs3wrMluQ2D1Q3t543WGJ6fZ8Yd4G4dk0TzF6enJCYfFm/efV6Ql69eL6axI25Z1+te7MactbEemMbBQDBO42B8tB+5OX+CHKaXSnC8E2wwkvYAUEPA2seBaf0rrTFLxXGWc7BBYXElKWc65nUVPwdZLJEhRcUmuaieH9FD+uzXGDOt7u9TpqrJuRTXCr3lEprae2j1xNi8YnLihnQmH6v3DttA952ze/p9tsAtWqFKLYuLO04xVweulO9q88IYOgfZIa7/sntmWAzGZCuWfRvZv1Q3zqj3kllkPQfoVF4wzM/mePHxLbvB73CICDdoNw8dfmCosI17BRmyPxqeA3aPzqJ9ZWOMqf+TWtygGytvlJ923c21ATGXfZvpHdwjetTzYv+tOHiBjte+4YQOhIa/2/Az+gsp09uHR/ReRX8S/wPFkjfV64IPmsSwoT0zJBY53N11IdX1s5JpUloZM/JL2O0o6VLIRZqtQldxxHsz117ayYnihViYR6TrML63QeicqF1aMn6hV51SzF9eZ0+To7rbaS3haMvaWkWneMq6YqpKoeegM1/u9WfOyNUEVqG6WZsKHsKLMtfWwdXBHAUtdFAUr2mMvfYU/waEYz9nGgqz74qcQ2B/g+1tUzk7ELl1F7Ug1NP3y+HmX+GBr70=

Steps to reproduce

I tried hard to reproduce the TypeError in the SFC playground, but was unsuccessful. However, the reproduction link is still a good demonstration of how we use Vue and might aid in identifying the problem.

There is only one button to interact with at the repro link called "ADD / REMOVE FILE". Click it to get an idea of what we are doing in our app. Essentially we are using user input to generate a mountain of source code. We have 250+ reactive components which in Vue 2, were not mounted, but reactively created source code in a C-like language. We are trying to migrate to Vue 3 and ran into this issue.

What is expected?

An array to always be returned from generatableFileModels.

What is actually happening?

flushJobs invokes the generatableFileModels computed property and in the process of computing the return value (which should always be an array of varying length), a component is mounted. This component was not mounted in Vue 2, but in trying to migrate, we are mounting it in Vue 3. Mount calls render2, which eventually calls generatableFileModels a second time, and on this call it returns null.

TypeError: Cannot read properties of undefined (reading 'findIndex') --> The undefined object is what is returned from a call to Proxy.generatableFileModels (uvc-package.js:218:35), which you can see was previously listed in this call stack.
    at Proxy.insertBefore (package.js:38:41)
    at Proxy.modelPairs (uvc-package.js:226:19)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at get value [as value] (reactivity.esm-bundler.js:1171:39)
    at Object.get [as modelPairs] (runtime-core.esm-bundler.js:3488:30)
    at Object.get (runtime-core.esm-bundler.js:3132:32)
    at lookupProperty (runtime.js:133:8)
    at Object.main (package-definition.hbs?import:143:79)
    at main (runtime.js:243:7)
    at ret (runtime.js:253:12)
    at Proxy.code (package.js:21:14)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at get value [as value] (reactivity.esm-bundler.js:1171:39)
    at Object.get [as code] (runtime-core.esm-bundler.js:3488:30)
    at Object.get (runtime-core.esm-bundler.js:3132:32)
    at Proxy.fileContents (generatable-file.js:103:37)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at get value [as value] (reactivity.esm-bundler.js:1171:39)
    at Object.get [as fileContents] (runtime-core.esm-bundler.js:3488:30)
    at Object.get (runtime-core.esm-bundler.js:3132:32)
    at Proxy.numLines (generatable-file.js:106:19)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at get value [as value] (reactivity.esm-bundler.js:1171:39)
    at Object.get [as numLines] (runtime-core.esm-bundler.js:3488:30)
    at Object.get (runtime-core.esm-bundler.js:3132:32)
    at generate-files-fab.vue:132:27
    at Array.forEach (<anonymous>)
    at Proxy.numLines (generate-files-fab.vue:131:19)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at get value [as value] (reactivity.esm-bundler.js:1171:39)
    at Object.get [as numLines] (runtime-core.esm-bundler.js:3488:30)
    at Object.get (runtime-core.esm-bundler.js:3132:32)
    at getter (runtime-core.esm-bundler.js:3615:17)
    at callWithErrorHandling (runtime-core.esm-bundler.js:173:36)
    at ReactiveEffect.getter [as fn] (runtime-core.esm-bundler.js:1739:28)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at job (runtime-core.esm-bundler.js:1801:37)
    at flushPreFlushCbs (runtime-core.esm-bundler.js:336:13)
    at render2 (runtime-core.esm-bundler.js:6280:9)
    at mount (runtime-core.esm-bundler.js:4474:25)
    at app.mount (runtime-dom.esm-bundler.js:1607:23)
    at createRenderlessComponent (create-renderless-component.js:92:26)
    at Proxy.phaseEnumType (uvc-package.js:308:11)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at get value [as value] (reactivity.esm-bundler.js:1171:39)
    at Object.get [as phaseEnumType] (runtime-core.esm-bundler.js:3488:30)
    at Object.get (runtime-core.esm-bundler.js:3132:32)
    at Proxy.phaseEnumVariable (uvc-sequence-item.js:131:36)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at get value [as value] (reactivity.esm-bundler.js:1171:39)
    at Object.get [as phaseEnumVariable] (runtime-core.esm-bundler.js:3488:30)
    at Object.get (runtime-core.esm-bundler.js:3132:32)
    at Proxy.computedInstanceVariables (uvc-sequence-item.js:50:14)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at get value [as value] (reactivity.esm-bundler.js:1171:39)
    at Object.get [as computedInstanceVariables] (runtime-core.esm-bundler.js:3488:30)
    at Object.get (runtime-core.esm-bundler.js:3132:32)
    at Proxy.almostAllInstanceVariables (encapsulator.js:13:19)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at get value [as value] (reactivity.esm-bundler.js:1171:39)
    at Object.get [as almostAllInstanceVariables] (runtime-core.esm-bundler.js:3488:30)
    at Object.get (runtime-core.esm-bundler.js:3132:32)
    at Proxy.allInstanceVariables (encapsulator.js:9:19)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at get value [as value] (reactivity.esm-bundler.js:1171:39)
    at Object.get [as allInstanceVariables] (runtime-core.esm-bundler.js:3488:30)
    at Object.get (runtime-core.esm-bundler.js:3132:32)
    at baseGet (index.js:464:14)
    at get (index.js:927:45)
    at Proxy.hasUvcCoverage (uvc.js:651:16)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at get value [as value] (reactivity.esm-bundler.js:1171:39)
    at Object.get [as hasUvcCoverage] (runtime-core.esm-bundler.js:3488:30)
    at Object.get (runtime-core.esm-bundler.js:3132:32)
    at Proxy.coverage (uvc.js:389:19)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at get value [as value] (reactivity.esm-bundler.js:1171:39)
    at Object.get [as coverage] (runtime-core.esm-bundler.js:3488:30)
    at Object.get (runtime-core.esm-bundler.js:3132:32)
    at Proxy.coverage (uvc-getters.js:165:35)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at get value [as value] (reactivity.esm-bundler.js:1171:39)
    at Object.get [as coverage] (runtime-core.esm-bundler.js:3488:30)
    at Object.get (runtime-core.esm-bundler.js:3132:32)
    at Proxy.baseGeneratableFileModels (uvc-package.js:184:14)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at get value [as value] (reactivity.esm-bundler.js:1171:39)
    at Object.get [as baseGeneratableFileModels] (runtime-core.esm-bundler.js:3488:30)
    at Object.get (runtime-core.esm-bundler.js:3132:32)
    at Proxy.generatableFileModels (uvc-package.js:218:35)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at get value [as value] (reactivity.esm-bundler.js:1171:39)
    at Object.get [as generatableFileModels] (runtime-core.esm-bundler.js:3488:30)
    at Object.get (runtime-core.esm-bundler.js:3132:32)
    at baseGet (index.js:464:14)
    at get (index.js:927:45)
    at Proxy.generatableFileModels (uvc.js:135:14)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at get value [as value] (reactivity.esm-bundler.js:1171:39)
    at Object.get [as generatableFileModels] (runtime-core.esm-bundler.js:3488:30)
    at Object.get (runtime-core.esm-bundler.js:3132:32)
    at Proxy.allGeneratableFileModels (uvc.js:144:19)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at get value [as value] (reactivity.esm-bundler.js:1171:39)
    at Object.get [as allGeneratableFileModels] (runtime-core.esm-bundler.js:3488:30)
    at Object.get (runtime-core.esm-bundler.js:3132:32)
    at Proxy._sfc_render (edit.vue:7:46)
    at renderComponentRoot (runtime-core.esm-bundler.js:914:44)
    at ReactiveEffect.componentUpdateFn [as fn] (runtime-core.esm-bundler.js:5720:34)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at instance.update (runtime-core.esm-bundler.js:5763:56)
    at callWithErrorHandling (runtime-core.esm-bundler.js:173:36)
    at flushJobs (runtime-core.esm-bundler.js:406:17)

System Info

System:
    OS: macOS 13.1
    CPU: (8) arm64 Apple M2
    Memory: 925.38 MB / 16.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 16.10.0 - ~/.nvm/versions/node/v16.10.0/bin/node
    npm: 7.24.0 - ~/.nvm/versions/node/v16.10.0/bin/npm
  Browsers:
    Chrome: 117.0.5938.149
    Safari: 16.2
  npmPackages:
    vue: 3.2.47 => 3.2.47

Any additional comments?

As I mentioned, we are trying to migrate to Vue 3 a lot of reactive code which was not previously mounted in Vue 2. The call of mount in the stack trace, if not made, would not kick off another call to generatableFileModels and therefor we'd not get the return of null. We want to keep the Options API format of our 250+ models. We obviously don't want to null check every call to computed properties in fear of null being return. Please help us find our way through this migration. Thank you.

skirtles-code commented 12 months ago

It isn't clear to me from the code provided that this is a Vue bug.

If you're looking for help with migrating your code then you could try GitHub Discussions or the Vue Discord server. This issues list is for reporting problems with Vue itself.

A computed property should not have any side effects, so it shouldn't be mounting a new Vue app. It looks like you're trying to create a reactive object with an enable property. In Vue 2 that could be achieved using Vue.observable(), but in Vue 3 it would be reactive({ enable: false }). There's no need to create a component instance to create a reactive object.