ConrabOpto / mst-query

Query library for mobx-state-tree
MIT License
116 stars 8 forks source link

Circular dependency #24

Closed benjaminkwokhuen closed 1 year ago

benjaminkwokhuen commented 2 years ago

I recently came across this problem when I'm writing my computed value. Consider the following example,

In some model,

import { getQueryClient } from 'contexts/StoreContext'

export const SomeModel = SomeModelBase.views((self) => ({
  computeValue(a: number, b: number): number {
    const { rootStore } = getQueryClient(self);
    // do stuff
  },
}));

In 'contexts/StoreContext':

import { createContext } from 'mst-query';
import { QueryClient } from 'mst-query';
import { RootStoreModel } from 'models/storeModels/RootStoreModel';
const env = {};

export const queryClient = new QueryClient({ RootStore: RootStoreModel });
export const { QueryClientProvider, useQueryClient, getQueryClient } =
  createContext(queryClient);

In this function, I want to get the rootStore using the getQueryClient. The queryClient is created in a separate file, which is imported to this file. The RootStoreModel is created using the createRootStore. Now, there is a circular dependency issue, since the SomeModelBase is part of RootStoreModel and SomeModelBase import from the StoreContext file to get the getQueryClient.

Of course, if you define all the models in a single file, it won't be an issue. But for large project with many models this is obviously not ideal.

I currently can get around this issue using the native getEnv like this:

export const SomeModel = SomeModelBase.views((self) => ({
  computeValue(a: number, b: number): number {
    const { rootStore } = getEnv(self).queryClient as QueryClient<IAnyModelType>;
    // do stuff
  },
}));

But doing so I lose typing. Any idea how I can solve this problem?

k-ode commented 2 years ago

Yeah, this isn't great. I haven't run into this yet because we only use getQueryClient in external stores. Maybe we can solve it on the type level somehow.

k-ode commented 2 years ago

I had a similiar problem today, and it's possible to type the root store with a view. (Typing the actual query client seems more difficult.)


// RootStore.ts
export const RootStore = createRootStore({
    itemStore: types.optional(createModelStore({ items: types.map(types.late(() => ItemModel)) }), {}),
}).props({
    count: types.number
});

export interface RootStoreType extends Instance<typeof RootStore> {}

// ItemModel.ts
import type { RootStoreType } from './RootStore';

export const ItemModel = types.model('ItemModel', {
    id: types.identifier,
    description: types.string,
    created: types.Date,
    count: types.number,
}).views(self => ({
    get rootStore(): RootStoreType { // typing the return type explictly is what makes this work
        const queryClient = getEnv(self).queryClient;
        return queryClient.rootStore;
    },
    get computeValue(): number { // also need to explictly type values that derive from rootStore
        return this.rootStore.count + self.count;
    }
}));