← Home

André Staltz


09 Feb 2016

Virtual DOM libraries like React and virtual-dom provide a declarative API for describing elements as the output of a system. This API may be limited when it comes to form fields where you may want to set the value of an input field as an assignment, not as an attribute. This led to solutions like “Controlled/Uncontrolled” components, which can be done also in Cycle.js.

In FRP there is a classical distinction between Signals (“values over time”, e.g. your age) and Event Streams (“occurrences” with no notion of current value, e.g. your birthdays).

The Virtual DOM interface allows us to describe element attributes only as Signals, not as Event Streams. This is why some use cases like assigning values to the input field are cumbersome with the Virtual DOM interface, because the round peg does not fit the square hole.

With RxJS in Cycle.js, we can create a component that takes an Event Stream of assignment events and solve this API problem, abstracting away the controlled/uncontrolled mechanism.

We make an Input(sources) component which takes some props for its corresponding <input>, and an Assign Event Stream. The output/sink is just the vtree$ for that <input>. It can then be used like this:

function main(sources) {
  // Intent
  const add$ = sources.DOM.select('.add')
  const type$ = sources.DOM.select('.field')
    .events('input').map(ev => ev.target.value);

  // Model
  const list$ = type$.flatMapLatest(text => add$.first().map(text))
    .scan((acc, curr) => acc.concat(curr));

  // View
  const input = Input({
    Props: Observable.of({className: 'field', style: {}}),
    // Clear the <input> when the Add button is clicked:
    Assign: add$.map(() => ''),

  const vtree$ = list$.combineLatest(input.DOM,
    (list, inputDOM) =>
        button('.add', 'Add'),
        ul(list.map(item => li(item)))

  return {
    DOM: vtree$,

Now we have a square peg (Event Stream) for a square hole (assigning values). See a JSBin example of this.

PS: this is not a blog post, it's a "big tweet".

If you liked this, consider sharing (tweeting) it too.