This lesson shows how we can create a more interactive app: a counter display with buttons to increment and decrement it. This reveals how we can use the RxJS scan() operator to remember past values and keep state.
We're starting to get the hang of Cycle.js. Let's do something now a bit more interactive. Let's create a counter app that has a number here displayed and has two buttons, Decrement and Increment. Whenever you click one of those buttons, it changes the number.
We are going to start by returning an object of sinks. We're going to have the DOM sink. I recommend that you always make an observable with just one event, and that event is a static virtual DOM tree, so that first we can create the right effect to see something here on the DOMs.
We can see how our app looks like, and then we can figure out later how to get user interactions through Read effects coming from the sources.
We're going to create a static virtual DOM tree. Now we're going to import the Button Helper function, a paragraph, and also a label.
Inside this DIV, I'm going to put a button with the text content of the increment. Besides, I can also give an optional first argument for this with a selector, saying "Decrements," the ID decrements.
If you inspect this element, it now has, on the DOM the ID Decrement. I can also give dot here to make a class. If you inspect it, you're going to see it has a class of Decrements.
I'm going to make the same type button for Increments, and I'm going to make a paragraph with a label saying the number zero, because this is just static content for now.
Now we have this static virtual DOM tree being displayed there on the DOM, and of course, it doesn't do anything with regard to user interaction, so we need to somehow capture these. We are interested in clicks on these buttons.
Let's get that by making an observable called Decrement Click Stream. It is from sources.dom. We're going to select the Decrement element, and we're going to be interested in click events on that Decrement element. Then almost the same thing, but for Increment button, it's just a matter of renaming this.
What does a decrement mean in our application? It doesn't yet carry any meaning for this number, so we're going to give it some meaning. We're going to make an observable called Decrement Action that interprets, "What does this click mean?"
We're going to map each of these clicks to -1, so now this decrement action represents, "What does each of these clicks mean?" That's the job of this declaration. Nothing else, just to interpret what does it mean for our app.
An increment action is increment click mapped to +1, and our end goal will be to create this observable called Number Stream. Let's imagine that we have Number Stream. It means that it's the evolving value of number over time.
If we have that, then we can take it and map each of these numbers to the virtual DOM tree displaying that number. We need to convert the number to a stream, so every time a number changes in the actual logic, then that number will be mapped to a virtual DOM tree displaying that number.
If we have this, then the view part is solved. So let me naively make an observable that has just one event, and that event is zero. It means that the number stream has just one event, and that event is zero, so it gets mapped to zero. That's what we see here. It could be 99, or it could be 10, so then you see that number shown there, because it's been converted.
Now we just need to bridge the gap between these actions and this number stream. With RxJS in general, you don't want to do something like this. You don't want to try to set the value of an observable, because that's not the Reactive pattern. It doesn't give you separation of concerns.
We want the number stream to declare how it works, and that means that we need to declare the whole future of this number stream only in the declaration. What we could do, naively, first, is by merging the decrement action into this number stream and also merging the increment action -- this doesn't make much sense for now -- but what this means is that this number stream is the blending of all of these observables together.
It has the observable with just the number 10. It has this one. This is the number 10, so this observable is that. Then it takes the -1s from the decrement action stream, and if you merge that one with this one, you will get this -- you get 10 and -1, and -1.
That's what merge does. It kind of blends these observables together. It means that if I click Decrement, it will come through this observable. It will be interpreted as -1, and it will be merged or blended into this number stream, which will become -1, and then it will show -1 there.
That's not really what we want to do. We want to add together 10 with -1, so if we plot Number Stream right now, it starts with 10, and it might see a -1, and it might see, let's say, another -1, and then it might see a +1 coming from the increment action.
We don't want to display this directly. We want to add this number with that number, and there's actually an operator for that called Scan. Scan let's us do that. I like to call Scan as the horizontal combinator, or the past combinator, because it allows you to combine this value with its past value, or horizontal, if you look at this diagram.
What we want is to get the previous value. Whenever we see a new value, we want to get the previous value and add those two together to get 10 - 1, which is 9. Then when we see another one, we want to add 9 with -1 to get 8, and so forth.
Scan allows us to do that, so we can give the previous. It takes a function that has two arguments, previous value and current value, and you can return something. We want to actually do previous value plus current value. I'm going to call this prev and curr.
That's what Scan does. If there's no previous, in the case of 10 here, 10 had no previous, it's going to return 10, and that actually works. Decrement clicks will now become -1s, and those -1s will be added with the previous value, which was 10, etc, and this does what we want.
This is actually how you keep state in Cycle.js. You use a scan to remember previous values and update those.