Nested calls to `runWithContext` resets the app for the rest of the top `runWithContext` call
Vue version
3.4.15
Link to minimal reproduction
https://stackblitz.com/edit/vue3-nested-runwithcontext?file=src%2Fmain.js
Steps to reproduce
- Call
app.runWithContext
- In the callback, use
inject
. It works as expected - In the callback, call again
app.runWithContext
- In the nested callback, use again
inject
. It works again as expected - Back to the first callback. use a third time
inject
. It doesn't work anymore
A situation where one can encounter such issue is in a combination between vue-router
guards and pinia
stores:
// simplified code from where I've encountered the issue
router.beforeEach(() => { // this is the top runWithContext()
const store = useSomePiniaStore(); // this is the nested runWithContext
const injected = inject('some-global'); // this doesn't work
});
To simply reproduce it on any app (with vue > 3.3
), run the following:
app.provide('global', { global: 'provided' });
app.runWithContext(() => {
console.log('first call', inject('global'));
app.runWithContext(() => {
console.log('nested call', inject('global'));
});
console.log('last call', inject('global'));
});
You'll see the following logs:
What is expected?
Calling inject
or other API expecting a scope should be working for the whole duration of the app.runWithContext()
.
What is actually happening?
When the nested app.runWithContext()
call finishes, the currentApp
variable is reset to null
, rendering it empty for the rest of the app.runWithContext
.
System Info
System:
OS: macOS 14.0
CPU: (8) arm64 Apple M1
Memory: 100.55 MB / 16.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 18.12.1 - ~/.local/share/volta/tools/image/node/18.12.1/bin/node
Yarn: 1.22.19 - ~/.local/share/volta/tools/image/yarn/1.22.19/bin/yarn
npm: 8.19.2 - ~/.local/share/volta/tools/image/node/18.12.1/bin/npm
bun: 1.0.23 - /opt/homebrew/bin/bun
Browsers:
Chrome: 121.0.6167.139
Safari: 17.0
npmPackages:
vue: ^3.4.9 => 3.4.10
Any additional comments?
In my case, I can just move the call to inject
to be the first line of the router guard, and this fixes the issue, but just because I can do it this way.
I think using a sort of stack like for the EffectScope
would work, so replacing the current:
runWithContext(fn) {
currentApp = app
try {
return fn()
} finally {
currentApp = null
}
}
With:
runWithContext(fn) {
let previousApp = currentApp;
try {
currentApp = app
return fn()
} finally {
currentApp = previousApp
}
}
should work, at least it did fix my test case. Not sure how that would work with async, but runWithContext
looks to only support synchronous calling, so that may be fine.