Subscribe on changes!

Nested calls to `runWithContext` resets the app for the rest of the top `runWithContext` call

avatar
Feb 2nd 2024

Vue version

3.4.15

Link to minimal reproduction

https://stackblitz.com/edit/vue3-nested-runwithcontext?file=src%2Fmain.js

Steps to reproduce

  1. Call app.runWithContext
  2. In the callback, use inject. It works as expected
  3. In the callback, call again app.runWithContext
  4. In the nested callback, use again inject. It works again as expected
  5. 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: Screenshot 2024-02-02 at 16 08 47

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.