Jan van Brügge & Alexander Späth
[1, 2, 3, 4, 5]
.map(x => x * 2)
.forEach(x => console.log(x));
Promise.resolve([1, 2, 3, 4, 5]) //Or from API
.then(arr => arr
.map(x => x * 2)
.forEach(x => console.log(x))
);
Observable.from([1, 2, 3, 4, 5]) //Or from a websocket
.map(x => x * 2)
.subscribe(x => console.log(x));
Make an initial request to the server and write the data to the DOM. Update the data with a websocket.
const ws$ = websocket$.map(message => message.payload);
const http$ = response$.map(res => res.body)
.map(xs.fromArray)
.flatten();
xs.merge(ws$, http$)
.fold((data, x) => data.concat(x), [])
.compose(debounce(50)) //Batch DOM updates
.subscribe({ next: updateDOM });
Cycle is like the physicist’s dream of a unified theory of everything, but for JavaScript.
Nick Johnstone
A Cycle.js app is just a function taking sources as input and returning sinks as result.
import xs from 'xstream';
import { div, button, p } from '@cycle/dom';
function Counter(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 vdom$ = count$.map(count =>
div([
button('.decrement', 'Decrement'),
button('.increment', 'Increment'),
p('Counter: ' + count)
])
);
return {
DOM: vdom$
};
}
import Counter from './counter';
function main(sources) {
const counter1 = Counter(sources);
const counter2 = Counter(sources);
const vdom$ = xs.combine(counter1.DOM, counter2.DOM)
.map(children =>
Some children
{ children }
);
return {
DOM: vdom$
};
}
Both counters are activated if you press any button
Isolate them!
import Counter from './counter';
function main(sources) {
- const counter1 = Counter(sources);
- const counter2 = Counter(sources);
+ const counter1 = isolate(Counter, 'counterA')(sources);
+ const counter2 = isolate(Counter, 'counterB')(sources);
const vdom$ = xs.combine(counter1.DOM, counter2.DOM)
.map(children =>
Some children
{ children }
);
return {
DOM: vdom$
};
}
A fractal state management tool for Cycle.js applications.
import xs from 'xstream';
function Counter(sources) {
const decrement$ = sources.DOM
.select('.decrement').events('click')
.map(_ => state => state - 1);
const increment$ = sources.DOM
.select('.increment').events('click')
.map(_ => state => state + 1);
const reducer$ = xs.merge(decrement$, increment$);
const vdom$ = sources.onion.state$.map(count =>
div([
button('.decrement', 'Decrement'),
button('.increment', 'Increment'),
p('Counter: ' + count)
])
);
return {
DOM: vdom$,
onion: reducer$
};
}
import Counter from './counter';
function main(sources) {
const counter1 = isolate(Counter, 'counterA')(sources);
const counter2 = isolate(Counter, 'counterB')(sources);
const vdom$ = xs.combine(counter1.DOM, counter2.DOM)
.map(children =>
Some children
{ children }
);
return {
DOM: vdom$,
onion: xs.merge(counter1.onion, counter2.onion),
log: sources.onion.state$ //what will be printed?
};
}
{
counterA: 5,
counterB: 3
}
Compare the parent state to the child state
{
counterA: 5,
counterB: 3
}
//childA
5
//childB
3
We can model this behavior
-- on the source (state$)
isolateSource :: ParentState -> ChildState
-- on the sink (reducer$)
isolateSinks :: (ParentState, ChildState) -> ParentState
We can model this behavior
-- on the source (state$)
get :: ParentState -> ChildState
-- on the sink (reducer$)
set :: (ParentState, ChildState) -> ParentState
We can model this behavior
const lens = {
get,
set
};
const halfLens = {
get: state => state.counterA / 2;
set: (state, n) => ({
...state,
counterA: n * 2
})
};
function main(sources) {
const counter1 = isolate(Counter, 'counterA')(sources);
const counter2 = isolate(Counter, {
onion: halfLens,
'*': 'counterB'
})(sources);
const vdom$ = xs.combine(counter1.DOM, counter2.DOM)
.map(view);
return {
DOM: vdom$,
onion: xs.merge(counter1.onion, counter2.onion),
};
}
interface TabbarState {
active: number;
tabs: TabState[];
}
interface TabState {
isActive: boolean; //Automaticly set by the lens
id: number;
name: string;
}
const selectionLens = {
get: state => state.tabs.map(t => ({
...t,
isActive: t.id === state.active
})),
set: (state, tabs) => ({
...state,
active: tabs.reduce((acc, curr) => {
return curr.isActive && curr.id !== state.active ?
curr.id : acc;
}, state.active),
tabs: tabs.map(omit('isActive'))
})
};
const TabbarComponent = makeCollection({
item: TabComponent,
itemKey: itemState => itemState.name,
collectSinks: instances => ({
DOM: instances.pickCombine('DOM').map(children =>
{ children }
),
onion: xs.merge(
xs.of(() => initialState),
instances.pickMerge('onion')
)
})
});
export default const isolatedTabbar = isolate(TabbarComponent, {
onion: selectionLens,
'*': null
});