Cycle.js

A functional and reactive JavaScript framework for cleaner code

Read the docs

Or try it live!

Your app and the external world as a circuit

cycle-nested-frontpage Created with Sketch. Sources Sinks main() DOM side effects HTTP side effects Other side effects pure dataflow

Cycle’s core abstraction is your application as a pure function main() where inputs are read effects (sources) from the external world and outputs (sinks) are write effects to affect the external world. These side effects in the external world are managed by drivers: plugins that handle DOM effects, HTTP effects, etc.

The internals of main() are built using Reactive programming primitives, which maximizes separation of concerns, providing a clean and fully declarative way of organizing your code. The dataflow is plainly visible, making it easy to read and understand the code.

Example

npm install xstream @cycle/xstream-run @cycle/dom
import {run} from '@cycle/xstream-run';
import {div, label, input, hr, h1, makeDOMDriver} from '@cycle/dom';

function main(sources) {
  const sinks = {
    DOM: sources.DOM.select('.field').events('input')
      .map(ev => ev.target.value)
      .startWith('')
      .map(name =>
        div([
          label('Name:'),
          input('.field', {attrs: {type: 'text'}}),
          hr(),
          h1('Hello ' + name),
        ])
      )
  };
  return sinks;
}

run(main, { DOM: makeDOMDriver('#app-container') });

Functional and Reactive

Functional means “clean”, and Reactive means “separated”. Cycle.js apps are made of pure functions, which means you know they simply take inputs and generate outputs, without performing any side effects. The building blocks are reactive streams from libraries like RxJS or xstream, which greatly simplify code related to events, asynchrony, and errors. Structuring the application with streams also separates concerns, because all dynamic updates to a piece of data are co-located and impossible to change from outside. As a result, apps in Cycle are entirely this-less and have nothing comparable to imperative calls such as setState() or foo.update().

Simple and Concise

Cycle.js is a framework with very few concepts to learn. The core API has just one function: run(app, drivers). Besides that, there are streams, functions, drivers (plugins for different types of side effects), and a helper function to isolate scoped components. This is a framework with very little “magic”. Most of the building blocks are just JavaScript functions. Usually the lack of “magic” leads to very verbose code, but since functional reactive streams are able to build complex dataflows with a few operations, you will come to see how apps in Cycle.js are small and readable.

Extensible and Testable

Drivers are plugin-like simple functions that take messages from sinks and call imperative functions. All side effects are contained in drivers. This means your application is just a pure function, and it becomes easy to swap drivers around. The community has built drivers for React Native, HTML5 Notification, Socket.io, etc. Sources and sinks can be easily used as Adapters and Ports. This also means testing is mostly a matter of feeding inputs and inspecting the output. No deep mocking needed. Your application is just a pure transformation of data.

Explicit dataflow

In every Cycle.js app, each of the stream declarations is a node in a dataflow graph, and the dependencies between declarations are arrows. This means there is a one-to-one correspondence between your code and minimap-like graph of the dataflow between external inputs and external outputs.

dataflow-minimap Created with Sketch. decrement$ increment$ action$ count$ vtree$ DOMSink DOMSource

function main(sources) {
  const decrement$ = sources.DOM
    .select('.decrement').events('click').mapTo(-1);

  const increment$ = sources.DOM
    .select('.increment').events('click').mapTo(+1);

  const action$ = xs.merge(decrement$, increment$);
  const count$ = action$.fold((x, y) => x + y, 0);

  const vtree$ = count$.map(count =>
    div([
      button('.decrement', 'Decrement'),
      button('.increment', 'Increment'),
      p('Counter: ' + count)
    ])
  );
  return { DOM: vtree$ };
}

In many frameworks the flow of data is implicit: you need to build a mental model of how data moves around in your app. In Cycle.js, the flow of data is clear by reading your code.

Composable

Cycle.js has components, but unlike other frameworks, every single Cycle.js app, no matter how complex, is a function that can be reused in a larger Cycle.js app.

nested-components Created with Sketch. Sources Sources Sinks Sinks

Sources and sinks are the interface between the application and the drivers, but they are also the interface between a child component and its parent. Cycle.js components can be simply GUI widgets like in other frameworks, but they are not limited to GUIs only. You can make Web Audio components, network requests components, and others, since the sources/sinks interface is not exclusive to the DOM.

Learn it in 1h 37min

Egghead_Logo_2

Got 1 hour and 37 minutes? That is all it takes to learn the essentials of Cycle.js. Watch this free Egghead.io video course given by the creator of Cycle.js. Understand Cycle.js from within by following how it is built from scratch, then learn how to transform your ideas into applications.

Supports…

Read the docs