Using AppStateMgr - dsriseah/ursys GitHub Wiki

At the time of writing, URSYS WebApps use AppCore Modules to act as a ViewModel that mediates communication between GUI components with other modules.

The AppStateMgr supports AppCore modules by providing a managed key-value store in with an EventEmitter-like interface.

  • When creating an instance of AppStateMgr, you give it a unique "group name" that identifies the collection of related state.
  • Each instance stores a React-compatible state object and enforces uniqueness of their keys. It also enforces case consistency internally: group names are forced to UPPER_SNAKE_CASE internally, and keys are forced to lower_snake_case.

Note

If looking through the GEMSTEP project for example uses in AppCore modules, they are using the class-state-manager.ts version of StateMgr.

Concepts and Conventions

The StateMgr class has both private methods and public methods.

  • The public methods do not have an underscore prefix (e.g. state()), and are simple access and subscribe methods. The use model is to expose a simple EventEmitter-style interface: subscribe(), unsubscribe(), sendState(), and getState().

  • The private methods are preceded with an underscore (e.g. _setState()) and are for use only by AppCore module authors. That's because these private methods are used to create complicated state dependencies that we want to hide in the public interface. For example, you might want to intercept the sendState() call do some special stuff by using the _interceptState() tap hook.

The StateMgr class is designed to work as a queue.

  • a call to StateMgr.sendState( stateObj ) requests a state change.
  • StateMgr puts stateObj into a queue. If there is a callback defined in the sendState() call, it's also pushed into a queue.
  • StateMgr dequeues and merges all pending state into single state object.
  • StateMgr notifies all subscribers with the merged state on the next thread tick
  • StateMgr invokes callbacks with no parameters (this is to discourage the use of "send then update" patterns that screw up async programming)
  • StateMgr invokes side effects that were added on the next thread tick

Note

StateMgr dequeues and processes state objects immediately rather than waiting a tick and allowing more AppCores to pile-on changes. In practice the queue is not very full as it's driven by user interface interactions that happen relatively slowly.

Common Operations

Instantiation & Initialization

This typically happens at the top of an AppCore module. It initializes a new key-value object named "token-editor" with two properties and their initial values. If there is an asyncronous loaded dependency, you could wrap this code in the LOAD_ASSETS phase to force it to happen before React initializes.

const STORE = new StateMgr('token-editor');
STORE._initializeState({
  script_tokens: [],
  script_name: ''
});

Sending and Retrieving State

These are what you would expect to see used in a class-style React.Component. See Using React with URSYS for more detail.

  • getState(), getState( propName ) - retrieves the entire state dictionary or a specific property.
  • sendState( stateObj ) - Queues a state change request
  • subscribe( callbackFunc ) - Receive state changes when they occur
  • unsubscribe( callbackFunc ) - Remove state change notification

Additionally, there is the "side effect" hook:

  • queueEffect( effectFunc ) - Queue a function to be executed after sendState() has completed notifying subscribers and they have all executed.

Static Convenience Methods

These static class methods are provided provide access to state groups by name.

  • StateMgr.GetStateManager( groupName ) - Retrieves the StateMgr instance by its name
  • StateMgr.GetStateData( groupName ) - Retrieves the state dictionary object by group name
  • StateMgr.NewInstance( groupName ) - Create/return a new instance of StateMgr if you hate the new keyword

Special State Manipulation

These methods are used to inspect and manipulate state as it is received. Use only inside the AppCore module that owns/initializes the StateMgr.

  • _interceptState( tapFunc ) - Your chance to filter the state change data before it's actually applied.

In your tap function, you might use these methods to do additional manipulation of state.

  • _setState( stateObj ) - Replaces the entire state object.
  • _mergeState( stateObj ) - Shallow merge of stateObj, returning the updated state.
  • _insertStateEvent( stateEvent, callback ) - your chance to add another state change action manually

Internal State Utilities

These methods provide object manipulation to ensure that each state object generated is a new one

  • _derefProps( stateObj ) - Given a stateObj, returns new obj with any array properties containing a new array with copied values
  • _cloneStateObject( stateObj ) - Returns a shallow clone, with _derefProps() applied beforehand.

Internal State Lifecycle Control

These methods implement the stages of state change in an orderly way, and are likely of no use to AppCore authors.

  • _isValidState( stateObj ) - Returns true if stateObj conforms to key naming standards. Does not check for nested objects; it's only top-level keys that are tested.
  • _enqueue( actionObj ) - Used by sendState() to queue the state object and any callback functions.
  • _dequeue() - Process the state queue, merging state and handling notification and side effects.

These utility methods support the _dequeue operation.

  • _doCallbacks() - Process the callbacks queued by _dequeue() immediately
  • _notifySubs( stateObj ) - Send a clone of each stateObj to every subscriber.
  • _doEffect() - Invokes all queued effect functions on the next Javascript thread tick, allowing time for them to
⚠️ **GitHub.com Fallback** ⚠️