Mobx-state-tree: Error 'Type alias 'IStore' circularly references itself' when used inside generator function

Created on 17 May 2018  Â·  10Comments  Â·  Source: mobxjs/mobx-state-tree

HI,

I have an issue using the type of a store inside an async action (Generator syntax using flow).

image

I can suppress the error using // @ts-ignore, but the interface is not recognized anymore, so no intellisense or type checking anymore.

image

is there a solution for this issue?

Many thanks

Most helpful comment

Note: if the circular dependency is not caused by the type definition, but by an action or view, this problem is circumventable by specifiying explicit return types, see for example the stores.js in https://codesandbox.io/s/rjl5m8jyqm versus https://codesandbox.io/s/0p2pw3xzqp

const WorkplaceProviderStore = types
  .model("WorkplaceProviderStore", {})
  .actions(self => {
    const fetch = flow(function*(url: string) {
      return yield getRoot<Instance<typeof RootStore>>(self).fetchSingle(
        "<url>"
      );
    });

    // circular dep because return type of fetch depends on RootStore, 
    // but RootStore's type depends WorkPlaceProviderStore
    return { fetch };
  });

export const RootStore = types
  .model("RootStore", {
    work: types.optional(types.late(() => WorkplaceProviderStore), { info: {} })
  })

But the following signature of fetch break the circular type dependency because the return type
of fetch no longer depends on the type of RootStore. Obviously you can put any other type than any there, as long as it is not RootStore or WorkplaceProviderStore` itself :-).

  .actions(self => {  
    const fetch = flow(function*(url: string): /* type annotation */ any {
      return yield getRoot<Instance<typeof RootStore>>(self).fetchSingle(
        "<url>"
      );
    });

All 10 comments

I tried to reproduce this error myself and couldn't get quite the same one (Got _"[ts] 'myActionName' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer."_ instead).

But the way I handle it is to capture the root instance as volatile-state. For example:

.actions(self => {
    const root = getRoot(self) as IRootStore;

    const createAsset = flow(function*(command: CreateCommand) {
      yield root.item.createItem(command);
    });
})

Edit: If you move your item around (ie. changing its root) you should be able to use the lifecycle hooks to update the reference.

That does not seem to work for me. I still get the same error.

otherStore.ts
image

rootstore.ts
image

When I use the following code, the error is gone, but the type information is also gone:
image

I Also, I have another issue that is related to this one: https://github.com/mobxjs/mobx-state-tree/issues/824. When using 2 yield statements in a generator function, the following error occurs:

'Cannot modify 'Store', the object is protected and can only be modified by using an action'

So at this moment, i'm forced to use promise.then().

I think the difference is in the way that IRootStore is declared. Rather than

export type IRootStore = typeof Root.Type;

I use something like

type RootStoreType = typeof Root.Type;
export interface IRootStore extends RootStoreType {}

(approach described here). As TypeScript treats interfaces differently to type aliases, I think it makes all the difference. Worth a try?

Regarding the 2nd issue, I haven't run into that - I can't find the exact situation that's described in the issue in my code though, so possibly it'll pop up sometime. I'd think using .then would be fine unless you were really needing to have middleware get involved between the calls.

I still get the same error. It seems to be the combination with the flow statement.
The issue with the flow statement is caused by a wrong import. I had imported flow from 'mobx' instead of' 'mobx-state-tree'

Note: if the circular dependency is not caused by the type definition, but by an action or view, this problem is circumventable by specifiying explicit return types, see for example the stores.js in https://codesandbox.io/s/rjl5m8jyqm versus https://codesandbox.io/s/0p2pw3xzqp

const WorkplaceProviderStore = types
  .model("WorkplaceProviderStore", {})
  .actions(self => {
    const fetch = flow(function*(url: string) {
      return yield getRoot<Instance<typeof RootStore>>(self).fetchSingle(
        "<url>"
      );
    });

    // circular dep because return type of fetch depends on RootStore, 
    // but RootStore's type depends WorkPlaceProviderStore
    return { fetch };
  });

export const RootStore = types
  .model("RootStore", {
    work: types.optional(types.late(() => WorkplaceProviderStore), { info: {} })
  })

But the following signature of fetch break the circular type dependency because the return type
of fetch no longer depends on the type of RootStore. Obviously you can put any other type than any there, as long as it is not RootStore or WorkplaceProviderStore` itself :-).

  .actions(self => {  
    const fetch = flow(function*(url: string): /* type annotation */ any {
      return yield getRoot<Instance<typeof RootStore>>(self).fetchSingle(
        "<url>"
      );
    });

Thanks for this response. This also works in my situation!
I only had to add the return type to the function and the error is gone.

In the meantime, I also found a workaround to get rid of the circular depencency, but the solution above is a lot better.

// My solution

const loadPlanning = flow(function*() {
    var rootStore = getRoot(self) as IRootStore;
    var token = yield rootStore.authenticationStore.aquireTokenForApiCall();
    self.planningList = yield activiteitenApi.fetchPlanningForActiviteitAsync(token, self.id );
    });

const loadPlanningAsync = () => {
      return loadPlanning();
    };

return { loadPlanningAsync };
// Current code

const loadPlanning = flow(function*():  any {
    var rootStore = getRoot(self) as IRootStore;
    var token = yield rootStore.authenticationStore.aquireTokenForApiCall();
    self.planningList = yield activiteitenApi.fetchPlanningForActiviteitAsync(token, self.id );
    });

return { loadPlanning};

Hah activiteitenApi, you're 🇳🇱/🇧🇪?

Op wo 29 aug. 2018 09:14 schreef abubberman notifications@github.com:

Thanks for this response. This also works in my situation!
I only had to add the return type to the function and the error is gone.

In the meantime, I also found a workaround to get rid of the circular
depencency, but the solution above is a lot better.

// My solution
const loadPlanning = flow(function*() {
var rootStore = getRoot(self) as IRootStore;
var token = yield rootStore.authenticationStore.aquireTokenForApiCall();
self.planningList = yield activiteitenApi.fetchPlanningForActiviteitAsync(token, self.id );
});
const loadPlanningAsync = () => {
return loadPlanning();
};
return { loadPlanningAsync };

// Current code
const loadPlanning = flow(function*(): any {
var rootStore = getRoot(self) as IRootStore;
var token = yield rootStore.authenticationStore.aquireTokenForApiCall();
self.planningList = yield activiteitenApi.fetchPlanningForActiviteitAsync(token, self.id );
});
return { loadPlanning};

—
You are receiving this because you modified the open/close state.

Reply to this email directly, view it on GitHub
https://github.com/mobxjs/mobx-state-tree/issues/823#issuecomment-416849875,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABvGhCq5Owc5KfmgBS4ra3Ap2C2C7fuQks5uVj9BgaJpZM4UCmQn
.

Yeah NL :-)

@mweststrate's workaround worked perfectly for me too. Had to explicitly specify the generator function's return type, which was IterableIterator<Promise<void>> in my case. e.g.

const foo = flow(function*(): IterableIterator<Promise<void>> {
  yield root.bar();
});

Also worked for me. I think it could be useful to add this information to the Typescript section of the README.

Was this page helpful?
0 / 5 - 0 ratings