Subscriptions

Subscriptions are a way to get values feed in your application from sources other than regular DOM node events (that you define directly in your views), like window events, timers, ajax calls and web sockets.

They exist pretty much the same way in Elm and they are analogous to creating any event source in Cycle.js, just the workflow is very different.

React

React doesn't come out of the box with anything to handle these kind of events. A [very] naive way to do it would be something like that:

import React from 'react'
import { render } from 'react-dom'
import YourApp from '../path/to/YourApp'

renderer = () => {
  const scrollPosition = window.pageYOffset || document.body.scrollTop

  render(<YourApp scrollPosition={scrollPosition}/>, document.getElementById('root'))
}
window.addEventListener('scroll', renderer)
renderer()

Here's a webpackbin for you to play around.

Of course, there are probably many libraries to solve this in a more elegant way, one that will handle this resources and simplify the rerendering process. If you're using Redux, you've probably heard of redux-effects.

Act

Subscriptions are pretty simple. If you have followed the chapter on event above you already know a bit about signals and their sources and processes. All you have to do to create a subscription is to create those. So if we wanna listen to the window's scroll, we should use the fromEvent source. Creating a source goes like this:

const source = fromEvent(window, 'scroll')

Note all DOM events have start and stop method, so when you pass then to your process, you have to call start. It exists because for some events, Act needs to stop the source, a DOM element that is removed from the DOM, for instance, so Act will call the method stop behind the scenes.

Now, there is a built in helper for processing the scroll position, called, conveniently, scroll. So you can just attach your source to it:

scroll(source.start())

When you create subscription you don't need to call the returning function yourself, you just pass it to Act and it will manage. The way to do that is to pass to Act's main method an object with the type of the action you want to trigger as the key. Here's the full code:

const subscriptions = {
  scroll: scroll(fromEvent(window, 'scroll').start())
}

main(view, { model, reducer, subscriptions })

With this code, Act will get the scroll position and send it to your reducer with an action like { type: 'scroll', payload: 123 } every time the scroll position changes.

In real life, you'll probably only care about certain specific scroll positions. To accomplish that you can simply put this logic in your reducer, but the encouraged way is to further compose your scroll process:

const moved = pipe(
  scroll,
  map((scrollPosition) => scrollPosition > 0)
  distinct
)

So in this process we first get the scroll position and turn it into a boolean, if the user has scrolled (>0). We finally call distinct, else we would get the true value everytime the user scrolled any pixel. distinct will filter our values to send them through only when they are different than the previous one (false becomes true and vice versa).

Another common usage here is to throtlle (a.k.a. debouce) this event, so you don't get too many rerenders which is memory and time consuming. In order to emit the value only once in every second, just do:

const throttledScroll = pipe(
  throttle(1000),
  scroll
)

The cool thing about this is that these functions can be used not only for scroll but for any sort of subscription you need.

One final and relevant comment is that Ramda's functions are pretty handy for creating your own processes. Take a look at some ideas:

Ex Transforms
map(head) [1, 2, 3] => 1
'abc' => 'a'
map(tail) [1, 2, 3] => [2, 3]
'abc' => 'bc'
map(prop('name')) {name: 'Spinoza'} => 'Spinoza'
{} => undefined
map(propOr('Descartes', 'name')) {name: 'Spinoza'} => 'Spinoza'
{} => 'Descartes'
filter(equals(5)) 5 => 5
6 => ()
filter(propEq('id', 7)) {id: 7} => {id: 7}
{id: 8} => ()
filter(contains(100)) [99, 100] => [99, 100]
[99, 101] => ()
fold(add, 0) [1,2,3] => 6
Subscription helpers

Many use cases are too common for people to have to reinvent the wheel every time. If you followed the examples above, take a look at the subscriptions folder, and you'll find plenty of ready made helpers. Here are two of them:

const subscriptions = { breakpoint, scroll }

The breakpoint subscription will give you a breakpoint for the current screen width, every time it changes. You can provide custom breakpoint config using brekpointWith. And scroll will give you the current scroll position, throttled to a reasonable amount of time. To know more check the documentation on subscriptions.

Note that the examples above used the scroll helper from processes, so you can compose your own, and here scroll comes from the subscription helpers, a more high level library that abstracts the whole signal - source and process.