A React-based, Javascript implementation of the ideas of FOREST - "Functional Observer REST" - replacing Redux, Reselect and Redux Saga in your dynamic Web pages.
Yes, Functional Observer is a programming model that is entirely based around state and pure functional transformation from state to state.
There are no actions, events, messages, commands, calls, queues, streams, etc. in Functional Observer, just state observing other state and updating itself. The React component state observes other states around it:
ComponentState
=
ComponentState
,
UserEnteredState
,
PeerComponentStates
,
RemoteJSONAPIStates
FOREST (Functional Observer REST) just adds HTTP to that model, via the RemoteJSONAPIStates
above.
This simple model replaces all the complexity of Redux, Reselect and Redux Saga with a highly declarative, visible set of state transformation functions, per component.
OK, here's a minimal example:
/* React render reading from `state` for this object's state */
/* Also has `this` for declaring widgets without actions or events! */
/* If the first, name arg matches a `state` fieldname, it reads from there */
function render(object){
return (
<div>
<hr/>
{this.textField('message')}
<br/><br/>
<span>Count: {object('counter')}</span>
{this.button('inc', {label: 'increment'})}
<br/><br/><hr/><br/>
</div>);
}
/* Mapping to above render function from a string matching */
/* the `is:` property of the backing state object. */
const renderers = {
'minimal': renderMin
};
/* Initial list of state objects */
/* The first one is taken to be the 'top' state object. */
forest.storeObjects(
[{ Evaluator: evalMin,
is: 'minimal',
message: 'Hello World!',
counter: 17
}],
renderers
);
/* Where all the domain logic goes: pure functional */
/* transform/reduction of visible state to new component state: */
/* `state = f(state, state.user-state)` */
function evalMin(state){
const incrementPushed = !state('inc') && state('user-state.inc');
return Object.assign({},
true && { message: state('user-state.message').toLowerCase() },
incrementPushed && { counter: state('counter') + 1 },
true && { inc: state('user-state.inc') }
);
}
An object reads the states around it (user-state
above, but also peer states and API
states) 'through' its own state, because in Forest, the dot .
can jump across to those
other objects to read (and then observe) them.
For example, above, state('user-state.message')
reads user-state
, which is simply the
UID of the user-state
object, then fetches that object and reads its message
property.
It then continues to be notified of changes to that object.
You can also discover peer component and remote API state in the same way
(e.g. state('selectorPeerComponent.choice')
or state('referenceData.choicelist')
).
The fact that Forest only uses state instead of actions or events means that detecting
change is done through comparison to previous state. For example, the expression above
incrementPushed = !state('inc') && state('user-state.inc')
detects that the user-state
inc
button is true
(down) while the known state was false
(up). The line
inc: state('user-state.inc')
then records the latest state for the next time around.
This consistent object graph traversal for all state (component, current user input, peer component, remote data), combined with the pure functional state transformation makes the domain logic simple and powerful.
It puts a lot of power into small state reducer or transformation functions and
expressions - one single dot .
can give a component access to all the state around it,
locally and remotely, and subscribe it to that state so that it's notified if it
changes. All the interactive logic is held in pure functions.
Thanks to my Tes colleague, Federico 'framp' Rampazzo, for the inspiration, example and base code from Storry!
The Eve Language is one of the closest examples I've found of a similar approach. The Red Language also has a number of aspects in common.