Signals: sources & processes

For many use cases you'll want to manipulate events in order to get a meaninful value from it. To do that you can totally just use a handler, as we saw in actions and use imperative programming to get it done. But there's a more elegant way out there, and you'll certainly become a better programmer by learning it. I'll call this "signals". I'll explain you what is a signal, without all the technical language people generally use to explain them, but I think that if you look at an example first, you'll just understand it:

import main from '@act/main'
import valueOnEnter from '@act/main/processes/valueOnEnter'

const view = (value) => (
  ['div', [
    value,
    ['input', {keyup: {update: valueOnEnter}}]
  ]]
)

main(view, { model: '' })

Looks simple, right? What this does is that is will send an action to the reducer with type update and with the value of the field as the payload only when the user hits enter, that's why we have this valueOnEnter there.

The reducer doesn't actually need to be defined, since we saw earlier that the default reducer will override the state with the payload sent.

But sometimes the goal of an action is not to change state, but to do some side effect. If the side effect doesn't need any info from the event, use plain actions, like we learned in the actions chapter. But if you need, you can use the following syntax:

{ event: [sideEffect, process] }

In this case, everey time the event happens, it will process it's value using process and finally it will call sideEffect with the history and the signal value. And if you need multiple side effects in the same event, you can always do:

{ event: [[sideEffect1, process1], [sideEffect2, process2]] }

Actually, all allowed syntaxes eventually translate to a list of pairs internally to Act.

If you want a quick way to know when to use each, look into the diagrams doc.

Now, going back to the valueOnEnter thing: there are plenty of helpers like this in Act, to cover most of the common use cases for web apps, just take a look at the code. If what you need is not there, don't worry, you can create your own processes.

But wait, I was talking about signals, and now I mentioned processes! The example was simple, but what all this means? Well, a signal has two parts, a source and a process. A source can be a DOM event handler (like in the example above) or can be an ajax call, a timer, a websocket connection, or anything that may give us values. And a process is just a function that gets the value that the source produces and returns a value that matters to us. Simple enough?

In our case, the source is the click DOM event of the input. The source is attached automatically by Act. This source give us DOM events. What the process valueOnEnter does is to get this event, fetch the value (like in event.target.value and only update when the event.keyCode is an enter key. So, as you can see, a process can not only manipulate the original value, but also filter it and only let us now about it when it is really important.

The cool thing about processes is that they are composable. This means you can define them piecemeal and then group them togheter. The two most important processes you need to know about are map and filter.

So map takes a value and returns another value. One example is the value process:

const value = map((event) => event.target.value)

With this you can do things like {change: {update: value}}, so on every input change it will send an action with type update and the value of the input as the payload.

And filter takes a value and decides whether this value is good or not. If it is, it will pass it along the signal, else it will not:

const isEnter = filter((event) => event.keyCode === 13)

So you can do {keyup: {update: isEnter}, and it will send an action if and only if the typed character is enter (13). But notice that filter only defines if the value is good, so it will pass event – the original value – as the payload. Since we rarely (if ever) want to pass raw events to reducers, we need somehow to combine these two things togheter.

If you're used to Rx, you already know you can just use method chaining for that, like in this example from RxJS docs:

const input = document.querySelector('#input')
const valueOnEnter = Rx.DOM.keyup(input)
    .filter((event) => event.keyCode === 13)
    .map((event) => event.target.value)

valueOnEnter.subscribe((value) => doSomethingWithValue(value))

In this Rx example, we're creating a source (Rx.DOM.keyup(textInput) and every time this event happens we're first mapping the event to a value (pluck is probably a simpler alternative for that BTW) and then only allowing values with more than 2 characters. Finally, the subscribe function is the "receiver" of this signal, getting all the values that pass the 2 characters test when they arrive.

In Act, things are a little bit different 'though. We don't use method chaing for that. We use simple functional composition instead. The two functions to help you do that are pipe and compose. They do the same thing, their only difference being the order of composition. pipe calls functions from left to right and compose from right to left.

If you're used to any Rx implementation, you'll probably prefer to use pipe all the time, but if you come from the functional world, you'll probably go with compose, since that's the most standard way of doing it in Haskell, and therefore, in mathematics. In mathematics, we would say (f ∘ g) to express a function that is the result of composing f and g, being them both functions. This means that if you call the composed function with x, x will be applied to g and then the result of g(x) to f, and then the resulting value will be f(g(x)). In Haskell the notation is very similar:

f = (+1)
g = (*2)
c = f . g
c(5) // => 11

As you can see . is the function composition operator. When we call the composed function (c), 5 is passed to g, yielding 10, and then to f, yielding 11.

Now back to Act. To compose the functions we created before, we could do:

const valueOnEnter = pipe(onEnter, value)

This will, given a DOM event as source, first check if the typed character is enter and, if it is, get the input value and send it the subscriber.

Note both pipe and compose are simply exported from Ramda with no change whatsoever. So, if you wanna know more, just check their docs.

Now you may be wondering why not just follow Rx's lead and use method chaining. The reasons are manifold, but let's stick to the two main ones:

1) Using simple composition makes... uhm... composition ... simpler. Although composing chained calls is possible in Rx (either by wrapping it in a function or using let), it is much nicer this way. You can always further compose your processes, like in this example:

  const reversedValueOnEnter = pipe(valueOnEnter, reverse)

This assumes you have a reverse process that maps a value to its reverse form (say abc => cba), and apply all processes already existing in valueOnEnter and then reverse, before reaching any subscriber. Pretty neat hum?

2) Allows the library to be more modular. With simple composition you can include only the process functions you really need and chop off some extra bytes from your final build. And this is specially relevant in the JavaScript world.

The final thing you need to know is that your reducer is defined as a subscriber of all signals you define in your Act views, so no extra effort is needed to connect your events to your reducer (to be perfectly clear, there's an intermediate step to transform the result to a "flux standard action", a.k.a FSA). Also, as I already mentioned the source is defined for you as well, which is simply the DOM element for the event you defined.

But in case you need to do it yourself, you can check the sources available on the signals/sources folder (or create your own, it is damn simple) and to subscribe you just call the returned function from your process, no subscribe function required. So the overall syntax of this system in sort of pseudo code is:

process(source)(subscriber)

Where source is a source (duh), and process is either one or a composition of many processing functions, like the reversedValueOnEnter that we defined above, and subscriber is a function that will do something useful with the the processed value.

Note that unless you call the resulting process with a subscriber, absolutely nothing will happen, not even the source will be really created (for DOM sources it means no addEventListener will be attached to the DOM element), which is probably what you expected anyway.

Now that you know the basics, you can also check the chapter about subscriptions to know how to handle things other than clicks and keyboard events, things that are not automatically attached to your views, like window events, timers, ajax calls and sockets.

Oh, and last, but not the least, now I can tell you that events with constant values are actually also signals, using the always process.