liveBook · Manning

Observable function

As explained in Building User-Friendly DSLs

An observable function is a MobX API function that converts a value or object into state whose changes can be tracked reactively.

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

How Building User-Friendly DSLs covers it

How deep it goes
Explains deep object conversion, automatic actions for function properties, and explicit handling required for circular references during deserialization.
Examples
Uses a TextValue editor, a rental AST passed to Projection, and a circular self-reference AST.
What it assumes
Assumes React event handlers and components while defining observable state through concrete MobX imports and calls.
What you can do after
Apply it via MobX to make UI state and AST trees observable while avoiding naive conversion of circular references.
How it connects
Connects observable with MobX actions, observer, React components, ASTs, references, and deserialization.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Building User-Friendly DSLs. The section links point to the parts of the book it draws on.