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.