Skip to content

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

@justinrlle

Description

@justinrlle

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions