Accessors and Writers
Background
The underlying design principal powering Electric UI is stateless operation. UI components, layouts, and behaviours don't have an underlying expectation of data over time.
To interact with data, we either read or modify our state. Electric UI holds all state in two StateTree
structures, one for what the UI thinks the hardware state is (hardware
), and one for what the UI optimistically wants the state to be (interface
).
This distinction is important as it allows front-end components to rapidly mutate values, behave as expected while the hardware communications link is unstable, or hold a block of state mutations which can then be batched prior to the send to hardware.
By default, components and accessors will use information from the optimistic committed
state.
As an example, with message IDs for rgb
containing a struct with r
, g
and b
integers, and a brightness
messageID with a brightness integer, the state tree will be the following:
{ rgb : { r : 255, g : 0, b : 128, }, brightness : 50,}
With the data represented correctly, Electric UI's automatic reconciliation of interface
and hardware
state-tree deltas mean you only need to connect the UI's components to the state information.
Data in - accessors
All data read by components from the hardware state use Accessors
.
Simple Accessors
They are often a simple string referring to the messageID. The Printer below would print 50
.
<Printer accessor ="brightness" />
Functional Accessors
Accessors can also be functional
, a function that takes the StateTree
and returns the respective element.
The Printer below is identical to the one above:
<Printer accessor ={state => state .brightness } />
Functional accessor syntax is used to read array elements directly, and custom datatypes are expressed as objects with members (which use dot-notation).
<Printer accessor ={state => state .rgb .r } />
As these accessors are functions, its possible to perform maths and logic in the accessor (though not recommended for heavy use).
This example takes the frequency value fanspeed_hz
and displays it in RPM by multiplying by 60.
<Printer accessor ={state => state .fanspeed_hz * 60} />
Reading custom codecs
The previous examples covered simple types, but the custom rgb
structure has member elements. To retrieve the red channel, use functional
syntax to retreive the rgb
object's member element r
.
<Printer accessor ={state => state .rgb .r } />
Data out - writers
To make a change to the hardware like changing the brightness of a light, we want to make a change to the current state.
All writers accept functions that provide the current state, and if applicable, a value representing the change from the component. The writer function must mutate the state to cause the update.
A button is the simplest component with a writer.
import { Button } from '@electricui/components-desktop-blueprint' <Button writer ={state => { state .brightness = 42 }}> Set Full Brightness</Button >
The writer in this button example is a function with only the state argument.
For a more advanced example, a Slider with functional
accessors requires its handles to have a name
property. Each handle's value and name will be collected and passed as an object in the Writer
.
import { Slider } from '@electricui/components-desktop-blueprint' <Slider writer ={(state , values ) => { state .rgb = { r : values .slider_handle_named_red , g : values .slider_handle_named_green , b : values .slider_handle_named_blue , } }}> <Slider .Handle name ="slider_handle_named_red" accessor ={state => state .rgb .r } /> <Slider .Handle name ="slider_handle_named_green" accessor ={state => state .rgb .g } /> <Slider .Handle name ="slider_handle_named_blue" accessor ={state => state .rgb .b } /></Slider >
In situations where a component is only mutating a single member of some structured data, instead of mutating the whole rgb
object in the state-tree, we'll directly interact with the r
member.
<Slider writer ={(state , values ) => { state .rgb .r = values .slider_handle_named_red }}> <Slider .Handle name ="slider_handle_named_red" accessor ={state => state .rgb .r } /></Slider >
Arrays
Arrays are treated the same as objects; functional accessors and writers allow for any combination of access and writing.
Given the starting state:
{ rgb : [255, 255, 255]}
A button with the following writer:
<Button writer ={state => { state .rgb = [0, 0, 0] }}> Turn off Light</Button >
or the following writer:
<Button writer ={state => { state .rgb [0] = 0 state .rgb [1] = 0 state .rgb [2] = 0 }}> Turn off Light</Button >
causes the underlying state to become:
{ rgb : [0, 0, 0]}
Custom components
When creating custom components we recommend using the hooks API to access and write state.