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'}},
assets.captions[index]
),
input(
{
style: {width: '100px', cursor: 'pointer'},
attrs: {
class: 'width',
type: 'range',
min: 40,
max: 720,
value: width
}
}
)
]),
div([
img({
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.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Sign up for Infrastructure as a Newsletter.
Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.