In order to avoid not consistent incoming data, I'm adding defaults in my models.
For example
const collectionModel = types.model({
type: types.optional(types.literal('collections'), 'collections'),
preview: types.optional(
types.model({
products: types.array(SelectableProduct)
}),
{}
),
data: types.optional(types.model({
items: 24,
perRow: 4,
global: types.optional(EvergreenQuery, {}),
curated: types.array(EvergreenItemSettings)
}), {})
})
This will allow me to create an instance of collectionModel
from an empty object
collection1 = collectionModel.create({})
When you are using references make sure to use safeReference
From the docs
* `types.safeReference` - A safe reference is like a standard reference, except that it accepts the undefined value by default
* and automatically sets itself to undefined (when the parent is a model) / removes itself from arrays and maps
* when the reference it is pointing to gets detached/destroyed.
*
* Strictly speaking it is a `types.maybe(types.reference(X))` with a customized `onInvalidate` option.
So if you are removing a node that is referenced somewhere else in the store, then that reference will get set to undefined.
From my experience, broken references are especially difficult to debug.
I like that mobx-state-tree forces me to have a defined structure. This makes me think of the structure before writing the logic which later simplifies writing the logic.
A Hacky solution
A hack that you could do is before saving the snapshot instantiate a new model. If it succeeds then save the snapshot, if not skip it.
const MyModel = types.model({})
onSnapshot(myModelInstance, s => {
try {
const testModel = MyModel.create(s)
if (localStorage) {
// here you can save the snapshot because you know for sure it won't break
localStorage.setItem('snap', JSON.stringify(s))
}
} catch(e) {
// log or something
// OR
console.log(
'snapshot failed because of',
difference(s, JSON.parse(localStorage.getItem('snap'))
)
}
})
// this methos does a deep difference between two objects
export const difference = <T>(orObject: object, orBase: object): T => {
function changes(object: any, base: any): any {
return _.transform(object, function(
result: any,
value: any,
key: any
): any {
if (!_.isEqual(value, base[key])) {
result[key] =
_.isObject(value) && _.isObject(base[key])
? changes(value, base[key])
: value;
}
});
}
return changes(orObject, orBase);
};
The diff method is very helpfull because it will make it easier to figure out what is the cause of the crash. In this way, the localStorage will have only valid snapshots and any invalid snapshot will log the cause of the issues.