I think I have a major breakthrough, I created code that allows to bind any React property to a Sodium Cell
or CellSink
. React events still need some minimum wiring, but no need to bubble event handlers all up to the container components like in ELM or Redux, just send the value straight to an FRP StreamSink
or CellSink
, super easy, like two-way binding, but referentially transparent. A dream come true?
I will soon push this to GitHUB.
By pushing all state changes to the react leafs, React will also update much more efficiently, because the Cell
s that are passed as props do not change, only their content changes.
I made a higher order component lift
that basically lifts a plain React component into a component that accepts a Cell
for each prop (but also a constant value to reduce overhead). This lifted component will listen to these cell props, putting all the latest values in the local state.
type Props<P> = {[K in keyof P]: P[K] | Cell<P[K]> };
abstract class Lifted<P> extends React.PureComponent<Props<P>, P> {
...
}
export function lift<P>(ChildClass: React.ComponentClass<P> | React.StatelessComponent<P>) {
return class extends Lifted<P> {
public render() {
return (
<ChildClass {...this.state} />
);
}
};
}
I also converted all intristic React elements.
This is all done in Typescript, so strongly typed (my company is called Strongly Typed Solutions, so I have to use Typescript )
As an example, I made a very simple data model:
export interface Person {
readonly firstName: string;
readonly lastName: string;
}
And a simple FRP circuit:
export class Person extends Circuit {
public readonly firstName : S.CellSink<string>;
public readonly lastName : S.CellSink<string>;
public readonly fullName : S.Cell<string>;
constructor(model: M.Person) {
super();
this.firstName = new S.CellSink(model.firstName);
this.lastName = new S.CellSink(model.lastName);
this.fullName = this.firstName.lift(this.lastName, (fn, ln) => `${fn} ${ln}`);
}
}
The React view to edit this FRP circuit:
import * as React from 'react';
import * as S from "sodiumjs"
import * as M from "../models/person"
import * as C from "../circuits/person"
import * as FRP from "../FRP"
export class PersonItemEditor extends React.PureComponent<C.Person> {
setFirstName = (e: React.ChangeEvent<HTMLInputElement>) => this.props.firstName.send(e.currentTarget.value);
setLastName = (e: React.ChangeEvent<HTMLInputElement>) => this.props.lastName.send(e.currentTarget.value);
public render() {
const { firstName, fullName, lastName } = this.props;
return (
<table>
<tbody>
<tr>
<td>First name: </td>
<td><FRP.input value={firstName} onChange={this.setFirstName} /></td>
</tr>
<tr>
<td>Last name: </td>
<td><FRP.input value={lastName} onChange={this.setLastName} /></td>
</tr>
<tr>
<td>Full name: </td>
<td><FRP.span>{fullName}</FRP.span></td>
</tr>
</tbody>
</table >
);
}
}
So basically you prefix every intristic React component with FRP.
when it needs to bind to an FRP
circuit (aka reactive-view-model).
The higher order component takes care of listening and unlistening when the React component is mounted or unmounted, or receives new props.
I will now make a more dynamic example (selecting, adding and removing persons), as well as switching.
Maybe this pattern should be called Model-View-Reactive, MVR, to make people think it has something to do with virtual reality