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