calmm-js / kefir.atom

Composable and decomposable reactive state with lenses and Kefir
MIT License
51 stars 6 forks source link

Can I pipe a kefir property into an AbstractMutable? #2

Closed undoZen closed 7 years ago

undoZen commented 7 years ago
urlhash.onValue((value) => rootAtom.lens('urlhash').set(value))

any alternative or shortcut?

polytypic commented 7 years ago

Yes, you can "pipe" a property into an AbstractMutable like that. In your case, piping "urlhash" to a "rootAtom", it is likely to be safe to use an onValue subscription for the purpose, because the "pipe" is only setup once and is live for the duration of the program. This may be something that you already understand, but, in general, however, one must be careful with onValue to avoid leaking subscriptions. The description starting here might also interest you.

No, there is no shortcut for that out of the box. In my JS projects I often have a file like monkey.js where I monkey patch libraries like Kefir with extensions I want. In my current project, I have (among others) the following extensions:

const o = Kefir.Observable.prototype
o.startWith = function (v) {return Kefir.constant(v).concat(this)}
o.mapK = function (f) {return K(this, f)}
o.view = function (...ls) {return K(this, L.get(P(...ls)))}
o.do = function (action) {return this.mapK(v => {action(v); return null}).startWith(null)}
o.into = function (settable) {return this.do(v => settable.set(v))}

The into operation above returns a Kefir property that immediately and always produces null. Such a property can be injected into Karet JSX directly and one use that to create components that provide outputs via reactive variables. Here is an example of a BMI component that takes width and height as input-output variables and bmi as an output variable:

const Slider = ({title, units, value, ...props}) =>
  <div>
    <div>{title}: {value}{units}</div>
    <input type="range" {...props} {...bind({value})}/>
  </div>

const BMI = ({weight = Atom(80), height = Atom(180), bmi = Atom()}) =>
  <div>
    <Slider title="Weight" units="kg" min={40}  max={140} value={weight}/>
    <Slider title="Height" units="cm" min={140} max={210} value={height}/>
    <div>BMI: {bmi}</div>
    {K(height, weight, (h, w) => Math.round(w/(h * h * 0.0001))).into(bmi)}
  </div>

Note that in the above there is no call to onValue—It is handled by Karet, which subscribes to properties when components are mounted and unsubscribes when they are unmounted.

Outside of BMI one can now share the bmi variable with other components if desired.

This form of flexible wiring via the use of reactive variables and side-effecting properties is somewhat unorthodox and I have not been doing it previously. I'm currently in the process of considering whether it would make sense to introduce alternatives to or redefine Atom and K with slightly different semantics that would make the above kind of wiring behave better.