mobx icon indicating copy to clipboard operation
mobx copied to clipboard

MobXGlobals.runId is not associated with actual run

Open andres-kovalev opened this issue 5 months ago • 6 comments

Hey,

Looks like nested deriviations override runId without restoring it later, so the runId cannot be used to identify the run. Is it intended?

How to reproduce the issue:

class Store {
  get foo() {
    console.log('foo', _getGlobalState().runId);
    return 'foo';
  }

  get bar() {
    console.log('bar', _getGlobalState().runId);
    const foo = this.foo;
    console.log('bar', _getGlobalState().runId);
    return foo + 'bar';
  }

  constructor() {
    makeAutoObservable(this);
  }
}

const store = new Store();

autorun(() => {
  console.log('autorun', _getGlobalState().runId);
  console.log(store.bar);
  console.log('autorun', _getGlobalState().runId);
});

Intended outcome:

autorun 1
bar 2
foo 3
bar 2
foobar
autorun 1

Actual outcome:

autorun 1
bar 2
foo 3
bar 3 // here we see the foo's runId when in fact we're in the same run of bar getter
foobar
autorun 3

Versions

at least the latest one If you are unable to use CodeSandbox for whatever reasons, please list here all relevant dependencies

If you encounter the issue after upgrading from MobX 4/5 to MobX 6, make sure you've applied the migration guide: https://mobx.js.org/migrating-from-4-or-5.html -->

andres-kovalev avatar Jul 17 '25 12:07 andres-kovalev

The relevant runId is captured in the derivations internal local state: https://github.com/mobxjs/mobx/blob/761a8dd4a658180fa5af546a155df994e78496bc/packages/mobx/src/core/derivation.ts#L177. Please note that _getGlobalState() is an internal API that you're not supposed to use directly.

mweststrate avatar Jul 17 '25 12:07 mweststrate

Isn't it exported to let developers develop new libraries? Is there any other way for the 3rd party function to detect if it has been called during the same run?

andres-kovalev avatar Jul 17 '25 13:07 andres-kovalev

Only within our own eco system. What are you trying to achieve?

On Thu, Jul 17, 2025, 15:03 Andres Kovalev @.***> wrote:

andres-kovalev left a comment (mobxjs/mobx#4570) https://github.com/mobxjs/mobx/issues/4570#issuecomment-3083996171

Isn't it exported to let developers develop new libraries? Is there any other way for the 3rd party function to detect if it has been called during the same run?

— Reply to this email directly, view it on GitHub https://github.com/mobxjs/mobx/issues/4570#issuecomment-3083996171, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAN4NBFJASIYBC77NXSGSUD3I6NJTAVCNFSM6AAAAACBXXMZG6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTAOBTHE4TMMJXGE . You are receiving this because you commented.Message ID: @.***>

mweststrate avatar Jul 17 '25 13:07 mweststrate

I work on a tool that allow me to use asynchronous calls in MobX computeds, something similar to fromPromise(), but with caching across re-runs:

public userId: number;

get userName() {
  const userData = sync(getUserData());

  if (userData.status === 'fulfilled') return userData.data.name;

  return 'loading...';
}

But to make sync() utility work correctly with cache I need to know if it's invoked during the same computed execution or re-execution. I understand it looks like a very special case. The only workaround I have at the moment is to patch global state and track trackingDeriviation change.

andres-kovalev avatar Jul 17 '25 14:07 andres-kovalev

You could leverage https://github.com/mobxjs/mobx-utils?tab=readme-ov-file#frompromise and store the returned observable somewhere?

mweststrate avatar Jul 18 '25 14:07 mweststrate

Yes, I know about this one, but it requires me to create more observables than I need an do it my hand. For instance, if I already have a flow with more than 1 step:

async function flow(arg1, arg2) {
  const intermediate = await step1(arg);
  const result = await step2(intermediate, arg2);

  return result;
}

I would need to create something like:

class Store {
  arg1 = undefined;
  arg2 = undefined;

  private get step1Promise() { return step1(this.arg1); }

  get step1() { return fromPromise(this.step1Promise); }

  private get step2Promise().{ return step2(this.step1.value, arg2); }

  get step2() { return fromPromise(this.step2Promise); }
}

The library I think of is designed to provide an API like this one:

class Store {
  arg1 = undefined;
  arg2 = undefined;

  get flow() {
    const intermediate = sync(step1)(this.arg1);
    const result = sync(step2)(intermediate, arg2);

    return result;
  }

(or similar)

andres-kovalev avatar Jul 29 '25 16:07 andres-kovalev