A Gentle Introduction to Cycle.js

Cycle.js is the most accessible FRP framework for JavaScript we’ve yet had the chance to be awed and confounded by. The upshot is it’s got plenty of training wheels to get you on your wobbly way. The down: it’s easier to implement than to comprehend, and the docs aren’t much of a help.

The best way to get moving is with Cycle’s own create-cycle-app (install it by name via NPM or Yarn, as you do). You’ll end up with something like what you see below, but with all of the necessary scaffolding in place to fire up a dev server or deploy a build. We’ve simplified it a bit for illustration.

import {run} from '@cycle/run';
import {div, makeDOMDriver} from '@cycle/dom';
import xs from 'xstream';

const main = function(sources) {
  const vtree$ = xs.of(
    div('My Awesome Cycle.js app')
  const sinks = {
    DOM: vtree$
  return sinks;

const drivers = {
  DOM: makeDOMDriver('#app')

run(main, drivers);

💡 Sources? Sinks? Cycle traffics in the trade lingo of environmental theory as an abstraction. Honestly it's not an abstraction that's going to help much. Think of sources as read effects, products of the Intent, and sinks as write effects, products of the Model.

The titular “cycle” in Cycle.js refers to a cyclical flow of streams between the user interface and the presentation logic of the app. The UI is an input for the logic. The logic is an input for the UI. Paradoxical, no? The trick up Cycle’s sleeve is that it knows how to seed this programmatic ouroboros and get the wheels turning by way of that run method we imported up top.

The starter output of create-cycle-app leaves much to be desired, not that we don’t appreciate simplicity. It’s just a little tough to get a grip on where to go next from a static hunk of unstyled text. So, in the interest of illustrating the essential basics, we made a Cycle version of our perennial slideshow app. Feel free to clone the repo or remix it on Glitch to kick the tires.

What follows is a quick tour in code of what makes Cycle spin.


const width$ = sources.DOM.select('.width').events('input')
  .map(e => e.target.value).startWith(410);

This is a stream. It’s made up of input events received in the DOM source on the width class. Think of it as an array, but rather than containing values in space it contains values in time. We startWith the value 410 because otherwise we’d be passing null downstream to whatever is consuming width$. The $ doesn’t do anything. That’s just a naming convention for streams.

💰 FYI: $$ is the convention for a stream of streams.


const vtree$ = xs.combine(index$, width$).map(([index, width]) =>

It’s a buyer’s market for reactive stream libraries. Create-cycle-app ships with xstream, a zippy, lightweight library purpose-built for Cycle by Cycle’s creator. At its simplest it can make streams like

const vtree$ = xs.of(
  div([div('A virtual DOM tree stream?'), div('Yup.')])

It’s well documented, and you can make it work well enough for most purposes without even learning half of the mere 26 methods it contains. That said you can use any library you prefer in its stead.


import {input, div, p, img} from '@cycle/dom';

Cycle uses a DOM abstraction library built on Hyperscript. The import above should feel comfortable for anyone coming from React. The implementation below…maybe a little less so.

div({style: {
  textAlign: 'center',
  fontFamily: 'sans-serif',
  fontWeight: '300'
}}, [
  div({style: {marginBottom: '20px'}}, [
    p({style:{color: '#858576', fontSize: '32px'}},
        style: {width: '100px', cursor: 'pointer'},
        attrs: {
          class: 'width',
          type: 'range',
          min: 40,
          max: 720,
          value: width
      style: {borderRadius: '12px', cursor: 'pointer'},
      attrs: {
        src: assets.slides[index],
        alt: assets.captions[index],
        width: `${width}px`

No, you don’t have to use inline style. Yes, you do need to export trees descending from a single element.

element(opts, children)

It’s more intuitive than it seems at first blush, is incredibly flexible, and supports basically everything under the HTML sun. And yes, you can use JSX instead, but since this is already included why not give it a shot.


Think of drivers as the bridge between the Cycle app and the world surrounding it, whether it be a connection to the human user via the DOM driver or an XHR via an HTTP driver.

import {makeDOMDriver} from '@cycle/dom';
const drivers = {
  DOM: makeDOMDriver('#app')

The boilerplate app ships with the DOM driver, but there are many others you can install. Also you can roll your own.

🚴🏼‍ Cycle.js is so philosophically different from other frameworks and so inherently deep that there's no wrapping arms around it in a single stretch. We'll dig into these topics and many others with more specificity in time, but this should be enough to get you started.

✖ Clear

🕵 Search Results

🔎 Searching...