The MobX observable function converts a value or object into reactive state. Components wrapped with observer can then react when that state changes. For an editable value, wrapping an edit-state object makes changes to inEdit visible to TextValue, so a click can switch the displayed span into an input. section 5.1.4
Make local edit state reactive
Import observable from mobx and pass an observable edit-state object to the component. State-changing event handlers remain actions; observable supplies the state that observer can track. section 5.1.4
Objects passed to observable are deeply observable: nested objects and arrays are converted as well. Wrapping the rental AST therefore makes changes inside its tree observable, provided the projection itself is wrapped with observer. Function-valued properties are automatically wrapped as actions, which lets a setValue callback update the underlying AST without separately wrapping that callback. section 5.1.6 section 5.1.6
Observe an AST at the application boundary
Pass observable(rental) as the AST supplied to Projection, then mark Projection with observer. Editing can call editState.setValue(newValue), changing the AST and allowing observed UI to update. section 5.1.6
Deep conversion does not automatically preserve reference relations. Applying observable to a circular AST can recurse until it reaches a maximum-call-stack error. Construct observable nodes explicitly and assign the reference afterward, or deserialize into observable objects while resolving references in a separate step. section 7.4 section 7.4
Listing 7.25 Deserializing to an observable AST
const { observable } = require("mobx") #1
const deserializeObservably = (serializedAst) => {
const id2AstObject = {}
const referencesToResolve = []
const deserializeInternal = (value) => { #2
if (isAstObject(value)) {
const astObject = observable({ #3
id: value.id,
concept: value.concept,
settings: {}
})
for (const propertyName in value.settings) {
astObject.settings[propertyName] =
deserializeInternal(
value.settings[propertyName])
}
id2AstObject[value.id] = astObject
return astObject
}
if (isSerializedAstReference(value)) {
const refObjectToFix = observable({}) #4
referencesToResolve.push([ value.refId, refObjectToFix ])
return refObjectToFix #5
}
if (Array.isArray(value)) {
return observable( #6
value.map(deserializeInternal))
}
return value
}
const deserializedAst = deserializeInternal(serializedAst)
referencesToResolve.forEach(
([ refId, refObjectToFix ]) => {
refObjectToFix.ref = id2AstObject[refId] #7
})
return deserializedAst
}
module.exports.deserializeObservably = #8
deserializeObservably
The deserializer creates each AST object as observable before recursively filling settings, records objects by ID, and uses separate observable placeholders for references to resolve later. section 7.4
