1

I'm trying to learn Cycle.js and must say that I'm finding it quite interesting. I'm trying to create a simple app where I have a input and a ul. Every time write some value to the input and I press enter I want to add a new li with the value to the ul, but its failing with the following error.

Uncaught TypeError: observables[_name2].doOnError is not a function

var view = function (state) {
    return CycleDOM.body([
        CycleDOM.input({ type: 'text', value: '' }),
        CycleDOM.ul({ className: 'text' },
            state.map(function (value) {
                CycleDOM.li({ innerText: value });
            }))
    ]);
};

var intent = function (DOM) {
    return DOM.select('input[type=text]').events('keydown').filter(function (ev) {
        return ev.which == 13 && ev.target.value.trim().length > 0;
    }).map(function (ev) {
        return ev.target.value;
    });
};

var model = function (action) {
    return action.startWith('');
};

var main = function (sources) {
    var actions = intent(sources.DOM);
    var state = model(actions);
    var sinks = {
        DOM: view(state)
    };
    return sinks;
}

var drivers = {
    DOM: CycleDOM.makeDOMDriver(document.body)
};

Cycle.run(main, drivers);
Soham Dasgupta
  • 5,061
  • 24
  • 79
  • 125
  • 1
    Setting program logic aside, view() needs to return an Observable, and the function inside state.map(...) should return something too. – juanrpozo Feb 26 '16 at 21:11

1 Answers1

2

First, it's good to see that people are interested in Cycle!

You are missing some points here and that's why you're having some struggle.

You might have not fully understood the concept of reactive programming yet. You should read The introduction to Reactive Programming you've been missing by the creator of Cycle and watch his videos about Cycle. They really help understanding how Cycle works on the inside.

Also, you could adopt the naming convention of Cycle, it really helps. A stream/observable should end with a $, like

var click$ = DOM.select('a').events('click');

As @juanrpozo said your main issue is in your view function because it returns a virtual tree instead of an observable of virtual tree. Also it is important that you understand that the state variable is an observable, not a javascript array. That's why I think you aren't comfortable with Rx yet. You think you're mapping an array, but actually your mapping an observable, hence returning another observable, not an array. But as the DOM sink should be an observable, that's perfect, you'd just have to wrap your VTree in the map:

var view = function (state$) {
    return state$.map(function (values) {
        CycleDOM.body([
            CycleDOM.input({ type: 'text', value: '' }),
            CycleDOM.ul({ className: 'text' }, values.map(function (value) {
                CycleDOM.li(value);
            }))
        ])
    };
}

Another issue is your state$ management. You have to understand that state$ is a stream of consecutive states of your component. It's kinda hard to explain this on stackoverflow, but if you read/watch the resources I sent you you'll get it without any problem.

I made you a jsbin of your code once corrected and changed a bit to respect a bit more the Intent/Model/View convention.

You also had other errors, but those were the most important.

chadrien
  • 498
  • 3
  • 12
  • Brilliant. Yes I'm still learning Rx, but enjoying it none the less. I knew I was supposed to return an `Observable` from the `view` function but was not sure how to map the children of the `ul`. Might I ask what does the `scan` function do? – Soham Dasgupta Feb 27 '16 at 05:50
  • I just ran your code and it says `values.map` is not a function. – Soham Dasgupta Feb 27 '16 at 06:07
  • 1
    `scan` allows you to apply an accumulator function: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/scan.md Here you have to understand that the state of your component is actually a list (array) of strings. What defines a todo list? It's list of things to do. That's your state. In your code the state was just a string. So to maintain and update that state, what we have to do is cumulate a list of strings. That's why the intent is "I add a new entry" and the model transforms this intent to "I add this new entry to the existing ones" and the view displays them – chadrien Feb 27 '16 at 11:59
  • 1
    Did you take the code from jsbin or did you just took a tiny bit of it? As it's working on jsbin I assume you only took a part of it, so in your case `values` might not be initialised (`values` is either `null`, `undefined` or `''`). That's what the `startWith([])` in my code is used for. – chadrien Feb 27 '16 at 12:00
  • I seem to understand the importance of `startWith` and `scan` and the view returning a `stream`. Thanks @chadrien. Is it possible we can discuss some more of this concept somewhere else? – Soham Dasgupta Feb 27 '16 at 17:34
  • You can find me (and a lot of others) on the Cycle gitter chan: https://gitter.im/cyclejs/core – chadrien Feb 27 '16 at 17:42