reactjsmobx-state-treemobx-react-lite

is it possible to call a MobxStateTree action from a child model?


I create a Mobx State Tree global state composed by a DataModel that contains a TranslationsDataModel. In DataModel I created a fetchDataset function that I would like to call in TranslationsDataModel but I get an error saying that fetchDataset is not a function and I don't understand why.

This is my code:

index.ts:

export const StateModel = types.model('StateModel', {
  data: types.optional(DataModel, {} as DataModelInstance),
})

export const stateInstance = StateModel.create()
export interface StateInstance extends Instance<typeof StateModel> {}
const RootStateContext = createContext<StateInstance | null>(null)
export const Provider = RootStateContext.Provider

export function useMst() {
  const state = useContext(RootStateContext)
  if (state === null) {
    throw new Error('')
  }
  return state
}

DataModel.ts:

export const DataModel = types
  .model('DataModel', {
    translations: types.optional(TranslationsDataModel, {} as TranslationsDataModelInstance),
  })
  .views((self) => ({
    get root() {
      return getRoot(self)
    },
  }))
  .actions((self) => ({
    fetchDataset: flow(function* fetchDataset<T>(dataUrl: string) {
      try {
        const dataset: T[] = yield fetchCsvDataset<T>(dataUrl)
        return dataset
      } catch (error) {
        console.log(`! Error: ${error}`)
        return []
      }
    }),
  }))

export interface DataModelInstance extends Instance<typeof DataModel> {}

TranslationsDataModel.ts:

const TranslationDatum = types.model('TranslationDatum', {
  key: types.string,
  text: types.string,
})

export const TranslationsDataModel = types
  .model('TranslationsDataModel', {
    translationsDataset: types.optional(t.array(TranslationDatum), []),
  })
  .views((self) => ({
    get root(): DataModelInstance {
      return getRoot(self)
    },
  }))
  .views((self) => ({
    getTranslation(key: string) {
      return '...'
    }
  }))
  .actions((self) => ({
    updateTranslationsDataset(dataset: Translation[]) {
      self.translationsDataset.replace(dataset)
    },
  }))
  .actions((self) => ({
    async afterCreate() {
      const translationsDataset = await self.root.fetchDataset<Translation>('translations.csv')
      self.updateTranslationsDataset(translationsDataset)
    },
  }))

export interface TranslationsDataModelInstance extends Instance<typeof TranslationsDataModel> {}

I used the stores in that way:

const Homepage = observer(() => {
  const {
    data: {
      translations: { getTranslation },
    },
  } = useMst()

  return (
    <div>{getTranslation('title')}</div>
  )
})

at line const translationsDataset = await self.root.fetchDataset<Translation>('translations.csv') I get Uncaught (in promise) TypeError: self.root.fetchDataset is not a function. What's the problem?


Solution

  • Here in your example fetchDataset function is defined in the DataModel, and you're trying to call it using self.root.fetchDataset from TranslationsDataModel, however, the fetchDataset function is not directly accessible through self.root and this should be the reason why the error occured

    solution:

    you should define an action in TranslationsDataModel that calls the fetchDataset function from the DataModel:

    export const TranslationsDataModel = types
      .model('TranslationsDataModel', {
        translationsDataset: types.optional(t.array(TranslationDatum), []),
      })
      .views((self) => ({
        get root(): DataModelInstance {
          return getRoot(self)
        },
      }))
      .views((self) => ({
        getTranslation(key: string) {
          return '...'
        }
      }))
      .actions((self) => ({
        updateTranslationsDataset(dataset: Translation[]) {
          self.translationsDataset.replace(dataset)
        },
        async fetchTranslationsDataset() {
          const translationsDataset = await self.root.fetchDataset<Translation>('translations.csv')
          self.updateTranslationsDataset(translationsDataset)
        },
      }))
      .actions((self) => ({
        async afterCreate() {
          await self.fetchTranslationsDataset();
        },
      }));
    
    export interface TranslationsDataModelInstance extends Instance<typeof TranslationsDataModel> {}
    

    You can now call self.fetchTranslationsDataset() within the afterCreate action to initiate the data fetching process using the function from the parent model DataModel.