The suggestions offered in this thread helped me move forward.
What I wanted to do next was to combine the two basic examples:
- increment / decrement a value
- incrementing this value adds an input box
- decrementing this value removes an input box
- compute the sum from an arbitrary number of input boxes
- remove specific input boxes on demand
I ended up using a Cell<Array>
, instead of Array<Cell>
.
The fun part came when I had to update the Array
inside the Cell
.
I ended up generating four different function streams (Stream<Function>
, each corresponding to one possible action on the data: add, change, remove specific, remove last) which I first merged, then accumulated the Cell<Array>
on.
FRP
type Input = { key: number, value: number };
export default function () {
let id = 0;
const newKey = () => id++;
// +/-
const i$ = new StreamSink<number>();
const d$ = new StreamSink<number>();
// triggered from UI
const change$ = new StreamSink<Input>();
const remove$ = new StreamSink<number>();
// function streams
const inc$: Stream<Function> = i$.map(_ => s => [...s, {key: newKey(), value: 0}]);
const dec$: Stream<Function> = d$.map(dec => s => s.slice(0, s.length + dec));
const chg$: Stream<Function> = change$.map(chg => s => s.map(t => t.key === chg.key ? chg : t));
const rmv$: Stream<Function> = remove$.map(key => s => s.filter(i => i.key !== key));
const inputs: Cell<Array<Input>> = inc$
.orElse(dec$)
.orElse(chg$)
.orElse(rmv$)
.accum([], (f, s) => f(s));
const sum: Cell<number> = inputs.map(arr => arr.reduce((v, i) => v + i.value, 0));
const count: Cell<number> = inputs.map(s => s.length);
return sodiumReaction({
up: () => i$.send(1),
dn: () => d$.send(-1),
change: k => e => change$.send({key: k, value: validNumber(e.target.value)}),
remove: k => _ => remove$.send(k),
},
{count, sum, inputs}
);
};
UI
export default (props) => (
<Provider>
{({up, dn, change, remove, state: {count, inputs, sum}}) => (
<Fragment>
<div>
<button onClick={up}>+</button>
<button onClick={dn}>-</button>
</div>
<div>
Inputs: {count} Sum: {sum}
</div>
<Fragment>
{inputs.map(({key, value}) => <div>
<input value={value} key={key} onChange={change(key)}/>
<button onClick={remove(key)}>x</button>
</div>)}
</Fragment>
</Fragment>
)}
</Provider>
);