Utilities to interoperate between Cycle.js and React
> Interoperability layer between Cycle.js and React
- Use React (DOM or Native) as the rendering library in a Cycle.js app
- Convert a Cycle.js app into a React component
- Support model-view-intent architecture with isolation scopes
```
npm install @cycle/react
`js
import xs from 'xstream';
import {render} from 'react-dom';
import {h, makeComponent} from '@cycle/react';
function main(sources) {
const inc = Symbol();
const inc$ = sources.react.select(inc).events('click');
const count$ = inc$.fold(count => count + 1, 0);
const vdom$ = count$.map(i =>
h('div', [
h('h1', Counter: ${i}),
h('button', {sel: inc}, 'Increment'),
]),
);
return {
react: vdom$,
};
}
const App = makeComponent(main);
render(h(App), document.getElementById('app'));
`
Other examples:
- Use React inside Cycle.js (CodeSandbox)
- Use Cycle.js to write a React component (CodeSandbox)
Read also the announcement blog post.
Install the package: ` Note that this package only supports React 16.4.0 and above. Also, as usual with Cycle.js apps, you might need xstream
Installation (click here)
bash`
npm install @cycle/react (or another stream library).
Use the hyperscript h ` function main(sources) { return { Alternatively, you can also use JSX or createElement ` function main(sources) {
Use React as the rendering library (click here)
function (from this library) to create streams of ReactElements:js
import xs from 'xstream'
import {h} from '@cycle/react'
const vdom$ = xs.periodic(1000).map(i =>
h('div', [
h('h1', Hello ${i + 1} times)
])
);
react: vdom$,
}
}
`:jsx
import xs from 'xstream'
const vdom$ = xs.periodic(1000).map(i =>
Hello ${i + 1} times
);
return {
react: vdom$,
}
}
`
However, to attach event listeners in model-view-intent style, you must use h which supports the special prop sel. See the next section.
Use hyperscript h ` function main(sources) { const count$ = increment$.fold(count => count + 1, 0) const vdom$ = count$.map(x => return { The sel
Listen to events in the Intent (click here)
and pass a sel as a prop. sel means "selector" and it's special like ref and key are: it does not affect the rendered DOM elements. Then, use that selector in sources.react.select(_).events(_):js
import xs from 'xstream'
import {h} from '@cycle/react'
const increment$ = sources.react.select('inc').events('click')
h('div', [
h('h1', Counter: ${x}),
h('button', {sel: 'inc'}),
])
)
react: vdom$,
}
}
` can be a string or a symbol. We recommend using symbols to avoid string typos and have safer guarantees when using multiple selectors in your Cycle.js app.
Use hyperscript h ` // React component
Pass event handlers as props to react components (click here)
and pass a sel as a prop. Use that selector in sources.react.select(sel).events(whatever) to have cyclejs/react pass an onWhatever function to the react component:js
import React from "react";
import ReactDOM from "react-dom";
import { makeComponent, h } from "@cycle/react";
function Welcome(props) {
return (
Hello, {props.name}
);
}
// Cycle.js component that uses the React component above
function main(sources) {
const click$ = sources.react
.select('welcome')
.events('pressWelcomeButton')
.debug('btn')
.startWith(null);
const vdom$ = click$.map(click =>
h('div', [
h(Welcome, { sel: 'welcome', name: 'madame' }),
h('h3', [button click event stream: ${click}])
])
);
return {
react: vdom$
};
}
const Component = makeComponent(main);
ReactDOM.render(
`
This library supports isolation with @cycle/isolate ` function child(sources) { function parent(sources) { const click$ = sources.react.select('foo').events('click') const elem$ = childSinks.react.map(childElem => return { react: elem$ }
Isolate event selection in a scope (click here)
, so that you can prevent components from selecting into each other even if they use the same string sel. Selectors just need to be unique within an isolation scope.js
import xs from 'xstream'
import isolate from '@cycle/isolate'
import {h} from '@cycle/react'
const elem$ = xs.of(
h('h1', {sel: 'foo'}, 'click$ will NOT select this')
)
return { react: vdom$ }
}
const childSinks = isolate(child, 'childScope')(sources)
h('div', [
childElem,
h('h1', {sel: 'foo'}, click$ will select this),
])
)
}
`
Use makeComponent ` Then you can use CycleApp If you are not using any other drivers, then you do not need to pass the second argument: `
(Easy) Convert a Cycle.js app into a React component (click here)
which takes the Cycle.js main function and a drivers object and returns a React component.js`
const CycleApp = makeComponent(main, {
HTTP: makeHTTPDriver(),
history: makeHistoryDriver(),
}); in a larger React app, e.g. in JSX . Any props that you pass to this component will be available as sources.react.props() which returns a stream of props.js`
const CycleApp = makeComponent(main);
Besides makeComponent It takes one argument, a run - run: () => {source, sink, events, dispose} As an example usage: ` source is an instance of ReactSource from this library, provided to the main sink is the stream of ReactElements your main events is a subset of the sinks, and contains streams that describe events that can be listened by the parent component of the CycleApp dispose is a function () => void Use this API to customize how instances of the returned component will use shared resources like non-rendering drivers. See recipes below.
(Advanced) Convert a Cycle.js app into a React component (click here)
, this library also provides the makeCycleReactComponent(run) API which is more powerful and can support more use cases. function which should set up and execute your application, and return three things: source, sink, (optionally:) events object, and dispose function.js`
const CycleApp = makeCycleReactComponent(() => {
const reactDriver = (sink) => new ReactSource();
const program = setup(main, {...drivers, react: reactDriver});
const source = program.sources.react;
const sink = program.sinks.react;
const events = {...program.sinks};
delete events.react;
for (let name in events) if (name in drivers) delete events[name];
const dispose = program.run();
return {source, sink, events, dispose};
}); so that events can be selected in the intent. creates, which should be rendered in the component we're creating. component. For instance, the stream events.save will emit events that the parent component can listen by passing the prop onSave to CycleApp component. This events object is optional, you do not need to create it if this component does not bubble events up to the parent. that runs any other disposal logic you want to happen on componentWillUnmount. This is optional.
Use the shortcut API makeComponent ` function makeComponent(main, drivers, channel = 'react') {
Recipe: from main and drivers to a React component (click here)
which is implemented in terms of the more the powerful makeCycleReactComponent API:js
import {setup} from '@cycle/run';
return makeCycleReactComponent(() => {
const program = setup(main, {...drivers, [channel]: () => new ReactSource()});
const source = program.sources[channel];
const sink = program.sinks[channel];
const events = {...program.sinks};
delete events[channel];
for (let name in events) if (name in drivers) delete events[name];
const dispose = program.run();
return {source, sink, dispose};
});
}
`
Assuming you have an engine `
Recipe: from main and engine to a React component (click here)
created with setupReusable (from @cycle/run), use the makeCycleReactComponent API like below:js`
function makeComponentReusing(main, engine, channel = 'react') {
return makeCycleReactComponent(() => {
const source = new ReactSource();
const sources = {...engine.sources, [channel]: source};
const sinks = main(sources);
const sink = sinks[channel];
const events = {...sinks};
delete events[channel];
const dispose = engine.run(sinks);
return {source, sink, dispose};
});
}
Use the makeCycleReactComponent `
Recipe: from source and sink to a React component (click here)
API like below:js`
function fromSourceSink(source, sink) {
return makeCycleReactComponent(() => ({source, sink}));
}
MIT, Copyright Andre 'Staltz' Medeiros 2018