State Values and State Managers

Often in an application, certain state is shared across multiple Components, and changes to the state might be requested at multiple endpoints. In the Edifice data flow, where state flows exclusively from parent to children, this requires setting one parent as the owner of that state, and passing this state to all its descendants. Changes to this state requires requesting the parent to change its state, which will trigger the render method call for all its descendents. This is potentially wasteful, since many of the intermediate Components are mere “message-passers” that only pass the state to their children and does not use it directly.

StateValue and StateManager provide an alternative model of state storage, with the principal advantages that

  • There is no need to pass both a “getter” and a “setter” for every state down the Component Tree. Indeed, if you choose to create a global StateValue or a global StateManager object, there is no need to pass any state down the Component Tree.

  • Changes to the value will trigger automatic re-render, just like calling set_state. However, only the nodes that actually use the state will be re-rendered.

A StateValue is a value (any Python object) that Edifice will keep track of. The principal methods of a StateValue are set and subscribe. Subscribe is the method you use to get the underlying value of the state value. It should be called only in the render function or after the component mounts. The method returns the underlying value, and also subscribes the component to all updates of the underlying value, so that changes to the value will cause the component to re-render. For example:

def render(self)::
    # Assume USER is a module-level variable
    user = USER.subscribe(self)
    # Assume balance is passed from a parent
    balance = self.props.balance.subscribe(self)
    return Label(f"{user}: {balance}")

This component will rerender whenever user or balance changes.

Set is the method you use to set the value of a StateValue. It will trigger re-renders of all subscribed components:

def on_click(self):
    USER.set(self.text_input_value)

Like all Edifice render triggers (the render_changes context, set_state), the StateValue set method is robust to exceptions. If any exception is thrown while re-rendering, all changes are unwound, including the StateValue, allowing you to properly handle the exception with guarantees of consistency.

A StateManager is very similar in concept to a StateValue; you can think of it as a key-value store for multiple values. StateManagers allow you to store related state together and update them in batch. Components subscribe to individual keys in the StateManager, and a StateValue tied to that key is returned. This can be used by the Component directly, or passed to children, who would not need to know about the underlying StateManager.

Class overview

StateValue(initial_value)

Container to store a value and rerender on value change.

StateManager([initial_values])

A key value store where changes to values will trigger a rerender.