Environment
Vuetify Version: 4.1.7
Vue Version: 3.5.40
OS: macOS 10.15.7 (current)
Steps to reproduce
- Open the reproduction link and switch to the preview
- Click Edit item to open the dialog
- Click Save to close it (pressing Esc or clicking the scrim does the same)
- The error is caught by
onErrorCaptured and shown in the alert; it is also logged to the console
The dialog in the repro is rendered with v-if="selected" and a flush: 'post' watcher
clears selected once the dialog closes — a "deselect after close" cleanup. That
combination is what puts the unmount inside the window described below; any post-flush
work that unmounts the dialog does it.
Expected Behavior
Closing a dialog that is unmounted as part of closing should not throw. Returning focus to
the activator is best-effort — if the dialog is already gone by the time the watcher
resumes, there is nothing to focus and it should be a no-op.
Actual Behavior
TypeError: Cannot read properties of null (reading 'activatorEl')
at VDialog.js:61
Reproduction Link
https://play.vuetifyjs.com/#...
Other comments
|
watch(isActive, async val => { |
|
if (!val) { |
|
await nextTick() |
|
overlay.value!.activatorEl?.focus({ preventScroll: true }) |
|
} |
|
}) |
overlay is the template ref to the inner VOverlay, so Vue sets it to null as soon as
VDialog unmounts. The watcher awaits a tick before reading it, and post-flush callbacks of
that same tick run before the await resumes, so the ref can be nulled in between:
close() -> isActive false, watcher body starts (overlay is still an object)
post-flush callback -> selected = null -> VDialog unmounts -> overlay = null
await resumes -> TypeError
Worth noting for anyone trying to reproduce it: closing and unmounting in the same tick
does not trigger it — the unmount disposes the watcher before its job ever runs.
Unmounting a microtask later (an await in the handler, Promise.resolve().then(...))
does not trigger it either — by then the watcher has already resumed with a live ref. Only
the post-flush window fails, which is probably why this shows up as an intermittent error
in production apps rather than something reliably reproducible.
The contentEl access a few lines above is guarded by its if (... && overlay.value?.contentEl ...);
this one is the only unguarded read.
Suggested fix, matching the surrounding style:
- overlay.value!.activatorEl?.focus({ preventScroll: true })
+ overlay.value?.activatorEl?.focus({ preventScroll: true })
This was reported before in #22142 (same error, same line) but closed as not planned
without a reproduction. Happy to open a PR if useful.
Environment
Vuetify Version: 4.1.7
Vue Version: 3.5.40
OS: macOS 10.15.7 (current)
Steps to reproduce
onErrorCapturedand shown in the alert; it is also logged to the consoleThe dialog in the repro is rendered with
v-if="selected"and aflush: 'post'watcherclears
selectedonce the dialog closes — a "deselect after close" cleanup. Thatcombination is what puts the unmount inside the window described below; any post-flush
work that unmounts the dialog does it.
Expected Behavior
Closing a dialog that is unmounted as part of closing should not throw. Returning focus to
the activator is best-effort — if the dialog is already gone by the time the watcher
resumes, there is nothing to focus and it should be a no-op.
Actual Behavior
TypeError: Cannot read properties of null (reading 'activatorEl')
at VDialog.js:61
Reproduction Link
https://play.vuetifyjs.com/#...
Other comments
vuetify/packages/vuetify/src/components/VDialog/VDialog.tsx
Lines 70 to 75 in 053542d
overlayis the template ref to the innerVOverlay, so Vue sets it tonullas soon asVDialog unmounts. The watcher awaits a tick before reading it, and post-flush callbacks of
that same tick run before the await resumes, so the ref can be nulled in between:
Worth noting for anyone trying to reproduce it: closing and unmounting in the same tick
does not trigger it — the unmount disposes the watcher before its job ever runs.
Unmounting a microtask later (an
awaitin the handler,Promise.resolve().then(...))does not trigger it either — by then the watcher has already resumed with a live ref. Only
the post-flush window fails, which is probably why this shows up as an intermittent error
in production apps rather than something reliably reproducible.
The
contentElaccess a few lines above is guarded by itsif (... && overlay.value?.contentEl ...);this one is the only unguarded read.
Suggested fix, matching the surrounding style:
This was reported before in #22142 (same error, same line) but closed as not planned
without a reproduction. Happy to open a PR if useful.