0
votes

I have a parent model and a state-child model, and I'd like to reference actions in the child model as such item.pdf.download().

I have created a workaround as such:

const pdf = types.model({ state: ... })
const Item = types.model({ ... }).volatile(self => ({ pdf: pdf.create({ ... }) }))

But it doesn't look like the intended way,

Is there a way to do nested module in mobx-state-tree?

1

1 Answers

0
votes

Yes, if fact everything that's not a primitive needs to be some MST model in order to be a part of a bigger model

// Parent
const ItemModel = types.model({
  pdf: types.optional(PdfModel, {}),
  // or when the child needs an initial data not provided by the snapshot
  // pdf: types.optional(PdfModel, { state: 'initial' }),

  // It can ofcourse be a list (or map) of child types, default value is []
  relatedPdfs: types.array(PdfModel),
})
  .views(self => ({
    get downloaded() {
      return self.relatedPdfs.filter(pdf => pdf.state == 'complete');
    }
  }))
  .actions(self => ({
    async downloadAll() {
      const tasks = self.relatedPdfs.map(pdf => pdf.download());
      const results = await Promise.all(tasks);

      return results;
    }),
  }));

// Child
const PdfModel = types.model({
  state:  types.optional(types.enumeration(['initial', 'downloading', 'complete', 'error']), 'initial'),
  url: types.maybeNull(types.string),
})
  .volatile(self => ({
    data: null,
  })),
  .actions(self => ({
    download: flow(function * download(url = self.url) {
      self.state = 'downloading';
      
      try {
        const data = await downloadTheData(url);
        
        self.state = 'complete';
        self.data = data;
       
        return data;
      }
      catch(e) {
        self.state = 'error';
      }
    }),
  }));

What might be a problem is that when you create an instance of the parent type you might have to provide a snapshot for the entire tree, or define default values for any child types, through types.optional() or allow undefined/null values through types.maybe

E.g. if we didn't have a types.optional and type.maybeNull for the child values we'd have to provide a snapshot that covered this

  const snap = {
    pdf: { state: 'initial', url: 'http://example.com' },
  };

  const item = ItemModel.create(snap);

But since we have defaults covered we don't need a initial snapshot

 const item = ItemModel.create();

You can ofcourse provide a snapshot anyway to override defaults