Fork
- updated to more recent version of cycle, including @cycle/state
- updated to React 16.9 api, including new lifecycles and context api
- merged onionify separate lib so import is
import {StateIsolator, Collection, makeFilteredListLens} from "react-cycle-connect";- added tests
- published at @iilab/react-cycle-connect
react-cycle-connect
An experimental library that allows using Cycle.js as a reactive dataflow framework for React. Written in TypeScript.
This little thing allows to define your app's view layer using regular React components, but implement the actual logic behind it using pure functional reactive approach, taking advantage of Cycle.js' Model-View-Intent pattern and its exceptional side effect control.
This isn't a custom implementation of Cycle pattern for React but a link to real Cycle.js instead, so any Cycle.js drivers and wrappers can be applied (e.g., cycle-onionify for state management). Also, for this reason, some knowledge about how Cycle.js works is assumed.
Note: This is an experimental lib and currently works with
Reactandxstreamonly. If all goes good, the support forpreact/inferno(instead ofReact) and forRxJS/most(instead ofxstream) is anticipated. Nothing prevents from havingreact-nativecovered too.
UPDATE (MAR 2018): Some internals overhaul is pending and it's currently waiting for Cycle.js to settle on hybrid push-pull reactivity and have internals rewritten to use Callbags instead of
xstream. So,react-cycle-connectis going to be using Callbags by default too.
Any feedback is highly appreciated!
Table of Contents
- react-cycle-connect
- Table of Contents
- Shup up and show me the code
- Installation
- Motivation
- How it works (architecture)
- Guide
- API reference
- Alternatives / Competitors
- FAQ
- Roadmap
Shup up and show me the code
Cycle.js's counter example ported over:
import React from 'react';
import ReactDOM from 'react-dom';
import xs from 'xstream';
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
// Your Cycle.js `main` function
function main(sources) {
const action$ = xs.merge(
sources.interactions.decrement.map(ev => -1),
sources.interactions.increment.map(ev => +1)
);
const count$ = action$.fold((acc, x) => acc + x, 0);
return {
props: count$.map(count => ({ count }))
};
}
// Your React component
function App({ interactions, count }) {
return (
<div>
<button onClick={interactions.decrement}>Decrement</button>
<button onClick={interactions.increment}>Increment</button>
<p>Counter: {count}</p>
</div>
);
}
// Some connection magic
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
// Render normally as it's a regular React app
ReactDOM.render(
<ConnectedApp />,
document.getElementById('root')
);
Also - there's official TodoMVC example with cycle-onionify for state management.
Installation
Using npm:
$ npm install --save react-cycle-connect
Using yarn:
$ yarn add react-cycle-connect
Motivation
The motivation to create this lib was based off the need to retain the React-style composability of the view layer using regular React components, reuse existing React components, make it (the lib) easily/incrementally adoptable in an ongoing React project, but also take advantage of the Cycle.js' MVI pattern and side effect control.
At the same time, while the most valuable thing from Cycle is the pattern itself, there are numerous available libs/plugins for Cycle.js to take advantage of, letting us take the best of both worlds.
This way, you can still use anything from React stack (e.g., react-router to compose your routed app screens), but describe the interactions as a dataflow, with pure functions and reactive streams. Most of React components are going to be "dumb" (and also stateless functional aka SFC) since all the backing logic and cross-component communication would be handled by a tree of Cycle.js programs.
Why not just use Cycle.js alone?
That is what I'd recommend considering in the first place. Its fast, its cool, the snabbdom (virtual DOM rendering engine behind Cycle's DOM driver) is goddamn fast and reliable. If you don't intend to use React components in any way, don't even get bothered. The react-cycle-connect is more about "introducing Cycle.js to React", not the other way around.
Is it a "Cycle.js in React" or "React in Cycle.js"?
More of a "Cycle.js in React". The app is bootstrapped by rendering a React component to the DOM, view is composed the React way. The root Cycle.js program is being run by rendering a root cycleConnect()ed component. Though, it doesn't prescribe how you write your Cycle.js programs, and aside from the DOM generation part, its just Cycle.js all the way.
How it works (architecture)
Every cycleConnect()ed component got a real Cycle.js program "attached" to it. The boundary between the two is props. From your Cycle.js program standpoint, props is a view. Cycle.js program can update props that React component will then render.
A bunch of nested React components in your app represent a "tree of React components". react-cycle-connect maintains a "tree of Cycle.js programs" that map to cycleConnect()ed components and takes advantage of React's context feature to provide links between the nodes. Each connected component provides scoped links to the children nodes .
New "Cycle nodes" are attached lazily, upon associated React component initialization (this is where React controls how Cycle programs are run). The above-mentioned "link" is just an object on React context that brings couple properties:
sourcesfor the child connected component'smainfunction; andsinkProxiesto subscribe to that component'smainfunctionsinks.
This is where something unusual for the Cycle.js happens - we subscribe to and unsubscribe from connected component's main function sinks based on React component's lifetime hooks. In Cycle.js, you normally just run the app at startup and let it run as an ongoing process. But this is where react-cycle-connect performs the sync between the two worlds.
Overall, aside from using React context (which is just an implementation detail) and some intense wrapping on the react-cycle-connect side to make laziness possible, react-cycle-connect tries to represent the entire tree of Cycle.js programs as close as possible to what you'd end up with by writing idiomatic Cycle.js with explicit calling of nested component's main functions from the parent component main function. The result of children components is always merged.
Aside from passing the sources to children components, react-cycle-connect also explicitly applies some internal wrappers to integrate with React component: lifecycle, props and interactions, that are scoped to a connected component's Cycle.js program only. It also explicitly runs the internal fnCallEffect driver for the root nodes by default - this is necessary for the interactions sink to work (because its supposed to run callbacks passed via props and that might be an effectful function).
Note that the entire internal "tree" of Cycle.js programs is maintained pure, including internal component-scoped wrappers, just as you would do with regular Cycle.js program.
Differences from idiomatic Cycle.js usage
Note: there's a difference in how'd you write the
react/react-cycle-connectapp vs pureCycle.jsapp. There's no misuse of Cycle.js per se, we just don't use it for rendering withreact-cycle-connect- and that applies certain implications to the approach.
React and Cycle.js, while both allow to create user interfaces, imply different approaches to the app architecture. The most important part here is that they are conceptually different when it comes to generating the VDOM.
In order to combine the two approaches nicely, there are certain deviations from how'd you write the Cycle.js-only app to allow for more optimal React DX.
The main difference from a pure
Cycle.jsway is that its not a Cycle program that technically generates VDOM - instead, there's React component to represent a piece of UI.Cycle.jsapproach to view/DOMevent handling is rather purist - view only defines the markup and nothing else (i.e., user interaction/event handlers are defined inside Cycle program itself and not as some view componentonClick/etc attribute). That is not a crazy random opinion but instead a very well thought and weighted out decision by Cycle.js team.react-cycle-connecttakes a different approach, which isn't that strict and is more in line with React, with callbacks amongstpropsand all that stuff. It enables the events/component callbacks handling via interactions.The view composition is obviously completely different. In
Cycle.jsyou run each component'smainfunction that (possibly) generates a VDOM node and then explicitly combine the pieces of markup into a single VDOM stream that is then given to a DOM driver. Withreact-cycle-connectyou compose the view layer with React components normally. You can just connect those to Cycle.js program to "outsource" the actual logic to it.
Guide
This assumes the preliminary knowledge of both React and Cycle.js (and xstream). It doesn't provide much of the insight on how these work, but is instead focused on how react-cycle-connect combines the two.
Basics
The result of cycleConnect() call is a HOC that optionally takes a React component (similar to that connect() function of react-redux) and returns a component that is augmented with backing Cycle.js program. This Cycle.js program is internally wrapped into a "Cycle Node" that, when initialized, is connected to a tree of Cycle.js programs. "Cycle Nodes" of every cycleConnect()ed components are linked together via React context.
Note, though, that there are no direct references from one component's Cycle program to another component's Cycle program, i.e., you're not invoking one main function from the other (this is perfectly possible though, but since we're not using DOM driver here, it would only handle non-DOM stuff). Instead, react-cycle-connect handles the linking behind the scenes, so the "sibling" Cycle programs are fed with the same sources and subscribe their sinks to the same sinkProxies.
There's essential concept of isolation in Cycle.js, so every cycleConnect()ed component accepts an isolate prop, that is directly passed to isolate function from @cycle/isolate package. Read more about Cycle.js isolation here.
Root Cycle
Every cycleConnect()ed component provides a cycleNodeLink object to its children through React context and also expects that cycleNodeLink from some parent component (not necessarily immediate parent).
That link object has the following shape:
{
// Sources to provide to our Cycle.js program's `main` function as an argument
sources: { ... },
// sinkProxies to subscribe to our Cycle program's `main` function sinks
sinkProxies: { ... }
}
You shouldn't be working with that directly but its important that something needs to provide the top-level sources and sinks in the first place. This is what root option of cycle connect for.
Also, the cycleConnect()ed components' programs (main functions) are lazily attached to already running upper-level Cycle.js program, so the root program has to be run.
So, root cycleConnect, aside from { root: true } option, also needs runFn defined, so that it knows how to run your top-level Cycle.js program. This would normally be just a run function from @cycle/run package (or @cycle/rxjs-run, or @cycle/most-run, depending on the reactive streams library in use).
You could just cycleConnect() your top-level App component like we did in the first example:
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
...
function main(sources) {
...
}
function App() {
return (
// Some JSX
...
);
}
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
Though this will work, its advisable to create a specialized RootCycle component just for this purpose:
// RootCycle.js
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
function main(sources) {
...
}
export const RootCycle = cycleConnect(main, {
root: true,
runFn: run
})(); // No component to wrap? Its okay, read ahead
export default RootCycle;
The top-level Cycle sources are actually drivers and wrappers, so root Cycle is also in charge of providing these drivers and wrappers to any Cycle.js program down the tree, as sources.
drivers option is the object with keys defining properties of the sources object, identical to how you'd do that in a regular Cycle.js program.
wrappers option accepts an array of wrappers (order might matter there, so its a user-land option).
Also, the main function is optional (because you might need a cycleConnect to be a root or for isolation only), so you can skip it, unless you have some logic to implement there.
Note that the
wrapperscan be passed to anycycleConnect, even non-root, butdriversoption is only valid forrootCycle and is ignored otherwise.
// RootCycle.js
import { run } from '@cycle/run';
import { makeHTTPDriver } from '@cycle/http';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const drivers = { HTTP: makeHTTPDriver() };
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
export default RootCycle;
RootCycle component is basically in charge of bootstrapping the "Cycle" part of your app here.
Then just wrap your App with it and e.g., render with ReactDOM:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import RootCycle from './RootCycle';
ReactDOM.render(
(
<RootCycle>
<App />
</RootCycle>
),
document.getElementById('root')
);
render option
You might have noticed in axamples above that we're not providing any component to a cycleConnect HOC, leaving empty braces instead:
const ConnectedComponent = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
Well, that is not a mistake - the wrapped component is optional. When a "component to wrap" is provided to cycleConnect(...)(ComponentToWrap), then if we render the connected component, the cycleConnect will also render ComponentToWrap internally.
Thats all good and works (and what you'll be using most of the time anyway), but sometimes you just want to keep your React Element tree a bit cleaner when the wrapped component would be a kind of noop anyway (just forwarding children and nothing else), or component is some technical/lower-level like RootCycle, or have some special rendering behavior.
So, we want to avoid seeing this in React Developer Tools:
<cycleConnect(SomeComponent)>
<SomeComponent>
...
</SomeComponent>
</cycleConnect(SomeComponent)>
You can use render option for that. It accepts the function with this signature:
(props) => ReactNode | null
If no component is provided for wrapping, nor render option is defined, the render option defaults to (props) => props.children || null function, so its either forwarding children if any or renders nothing.
If both render option is provided and component-to-wrap is passed, then render takes the precedence.
Note: Why not use the
renderoption in place of wrapped component all the time? Well, its technically possible, espeically if your React component is a stateless functional component anyway.But regular HOC wrapping is just common technique in React world and is nicely composable when you have other HOCs than
cycleConnect.And is cleaner too - you might be prototyping your app as a set of
Reactcomponents first, without even involvingreact-cycle-connect, then "connect" them all at once, or introducereact-cycle-connectincrementally. Whatever :)
displayName option
Your component's displayName is by default automatically assigned to the following (depending on whether the wrapped component is provided, or render option, or neither:
cycleConnect(WrappedComponentNameHere)when WrappedComponent is passedcycleConnect(customRenderFn)whenrenderprop is defined (and is truthy)cycleConnect(defaultRenderFn)otherwise
You can override that like with any regular React component:
const ConnectedComponent = cycleConnect({
...
)(Component);
ConnectedComponent.displayName = 'ConnectedComponent';
But there's also a displayName option to set the generated (connected) component displayName, which looks a bit cleaner:
const ConnectedComponent = cycleConnect({
...
displayName: 'ConnectedComponent'
)(Component);
Inside the Cycle
react-cycle-connect really assumes that you know how to use Cycle.js. Its really a very simple yet very elegant pattern, so make sure to familiarize yourself with it.
Quick recap on what Cycle.js main function is. Its basically a pure function with the signature:
main :: (Sources) => Sinks
It accepts sources as an argument and returns sinks and describes a dataflow using composition of reactive streams and their transformations with pure functions.
Please note that the
mainfunction is only run once perReactcomponent lifecycle. It doesn't run again on rerendering or something. This is because the actual call tomain(...)is to just build up a dataflow, not to get some rendering data.The data flows through reactive streams, from
sourcestosinksof themain, until itssinksare unsubscribed from parent Cycle program'ssinkProxies(this happens when React component is unmounted).
Drivers
The driver is supposed to run side effects for a Cycle.js program. Its outside the program itself, supposed to be impure and connects your Cycle app with the "outer world". This is an essential concept and is described in a great detail in Cycle.js docs on drivers.
There are no differences from idiomatic Cycle.js in how react-cycle-connect handles drivers - it just hands it over to @cycle/run directly. There is only one react-cycle-connect driver (an fnCallEffect driver) that is always added internally by the cycleConnect({ root: true })ed component - it allows to invoke functions outside the main that is to be maintained pure.
Wrappers
The wrapper is a rather simple thing - its just a function that takes main function that wraps it with the other main function that might in turn add some logic in between:
function someWrapper(originalMain) {
return wrappedMain(sources) {
// Can do something here, like transforming `sources` somehow
// ...
// Invoke the original `main` internally to obtain its sinks
const sinks = originalMain(sources);
// Can do something to `originalMain`s sinks here
// ...
return {
...sinks,
someOtherSink: xs.of({ ... }) // Can add a new sink
};
}
}
It is important that the wrapping main is maintained pure, so that your entire program is pure. If you need some effectful logic to run in a wrapping main, you should consider using driver for that. Wrappers, just like any regular main function, can internally use sources that are created from drivers.
Wrappers can also add arbitrary sources and could expect certain sinks to be returned from the Cycle program, depending on particular wrapper implementation.
Wrappers are usually created to encapsulate some non-business-logic stuff, you'd be normally describe anything related to the app itself in the "original" main function.
Most of react-cycle-connect (aside from cycleConnect(...) HOC itself) is implemented with wrappers, that are created for each cycleConnect()ed component automatically:
reactPropsWrapper(to mess around with React component props)reactLifecycleWrapper(a gateway to React component lifecycle streams)interactionsWrapper(to route the view events back from your wrapped React component aka view, and pass the events upstream, to parent components)
Sources
This is a Cycle.js program's input. main function shouldn't care whether something on the sources is a driver or some custom thing provided by one of the wrappers.
For root connected component, the sources are drivers directly plus whatever wrappers add to them.
Sinks
This is a Cycle.js program's output.
react-cycle-connect invokes your cycleConnect()ed component's main function, takes its sinks and subscribes the sinkProxies passed from the parent component to these sinks. Its how react-cycle-component is "attaching" the new component to a tree of Cycle programs.
If there's no parent component (thus, no sinkProxies) and your component is a "Root Cycle", then the sinks are handled to @cycle/run to "run" as with a regular Cycle.js app.
React props
The cycleConnect()ed React component is essentially a view for the associated Cycle.js main function. And react-cycle-connect encourages the view to be a "dumb" stateless function that takes props as an argument ad returns something of ReactNode type (i.e., ReactElements, null, etc, whatever React component renders).
Note: The wrapped
Reactshouldn't necessarily be a stateless functional component (SFC), you could have anyReactcomponent wrapped, but its advisable to use functions for the most part as the whole point ofreact-cycle-connectis to useCycle.jsfor anything that is not a view.
So, the props is the only communication channel from our Cycle.js part (the connected component's main function) to connected React component itself. We therefore need to pass the props from our Cycle program to the view (a React component) somehow.
This is where reactPropsWrapper comes into play and provides props source and sink for a main function. Its local to a component, so no other components will share the same props source or sink.
props sink
The props sink on cycleConnect()ed is there to pass the props to the view:
function main(sources) {
return {
props: xs.of({ greetable: 'World' })
};
}
// Use the passed `greetable` prop
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
Remember, that the value you map to props sink should be an object of new props and that updated props will be merged with existing component props like:
const updatedProps = Object.assign({}, prevProps, newProps);
This means that you can easily overwrite any props you want and leave the others intact.
Also, note above that we're passing the props through an Observable stream and that might be synchronous (like in our example with a stream of static data that is sent immediately) but could also be asynchronous. In that case the value of some prop might be not yet available on the first render:
function main(sources) {
return {
props: xs.of({ greetable: 'World' }).compose(delay(3000))
};
}
// Trying to use the passed `greetable` prop
// But whooops, no `greetable` yet :(
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
So either provide a default value for that async prop's Observable or perform additional checks in the view, when rendering.
Note: When messing with the
propsin yourmain, remember that any props that are passed from outside as an input to aReactcomponent, are also there (including callback functions, if there are any) and are also merged upon updates from outside (oncomponentDidUpdate).
Lets summarize: the props from the props sink are passed from the main function to the view (React component).
props source
Not only you'll need to provide the props to the view for rendering, but oftentimes you'd need your props to take part in transformations/calculations in the main function itself. For that reason, reactPropsWrapper provides the props source as well.
It allows you to have an Observable stream of your component's input props. Note that the sources.props is of type ReactPropsSource and to take the actual stream of props, use its props$ attribute:
function main(sources) {
const name$ = sources.props.props$.map(props => props.name);
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
function MyComponent(props) {
return (
<div>
Hello, {props.name}! Your father is Mr. {props.father}.
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
...
// Somewhere else:
<MyConnectedComponent name="Luke Skywalker" father="Darth Vader" />
// renders:
//
// <div>
// Hello, LUKE SKYWALKER! Your father is Mr. Darth Vader.
// </div>
The new value on props$ is emitted every time props change. This could be overkill for the most cases, as you would only need just few props for your dataflow.
This is what ReactPropsSource.pluck() method for, it accepts the name of the nested prop (will return undefined if the stream value is not an object) and returns a stream of that prop only that is only got new value once this particular prop changes:
function main(sources) {
const name$ = sources.props.pluck('name');
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
The pluck()ed stream also performs shallow comparison and does dropRepeats() behind the scenes, so identical values don't cause a new value emitted on an Observable.
Note that you don't need to always pull all props from your main function's sources.props just for the sake of it to just forward to the props sink directly, you better only pick those you need in a dataflow with ReactPropsSource.pluck(). The props passed from outside will be available on the view normally. You can overwrite them from inside your main though.
There's also ReactPropsSource.select() method that also accepts a nested prop name. The difference is that the result is not an Observable stream of prop values but the ReactPropsSource instance itself that is scoped to that piece of props:
function main(sources) {
const name$ = sources.props.pluck('name'); // Stream of strings
const nameSource = sources.props.select('someNestedObject'); // Scoped ReactPropsSource
const someNestedObjProperty$ = nameSource.pluck('someNestedObjectProperty');
...
}
This is useful when you want to pluck() a stream of some sub-prop from deeper nested objects.
Note: Taking deep property stream this way isn't really nice currently as requires quite a bit of boilerplate - this part of API is already being thought out further and reconsidered.
Interactions
So far so good, but rendering the props isn't the only exciting thing we want to do with React, right? We also want to handle events coming from the view(e.g., the DOM).
The way react-cycle-connect handles events and nested components' callbacks is the biggest deviation from an idiomatic Cycle.js app using DOM driver. It refrains from a holistic "no handlers in the view" approach for a few reasons:
- We need something more in line with
Reactas we're using React here - We're not always handling DOM events but also need to handle some children components data/events passed back. We can't pass streams like in regular Cycle.js app, in
Reactwe do it via callbacks that are provided amongst children components'propsand we needcycleConnect()ed components to be compatibleReactcomponents at all levels.
This is what "interactions" feature of react-cycle-connect is for. It provides the unified interface for parent-child components communication and event handling that is aligned with a regular event/callback handling model on React side but also allows to access those events in form of Observable streams inside the main function.
Events triggering by the view
Every cycleConnect()ed component has some additional react-cycle-connect-specific props injected, so they are available on your view.
These are interactFn which is a function that takes the interactionType (a string) and optional predefinedValue (of any type) and returns a function that will be a callback for your event or some child component prop function.
So, the signature is:
(interactionType, predefinedValue?) => (value) => void
Its supposed to be called imperatively in your view and is available as props.interact:
function MyComponent({ interact }) {
return (
<div>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
For the sake of being cleaner (and for better TypeScript support), react-cycle-connect also leverages the ES2015 Proxy feature to provide the object with dynamic properties, each representing an interact with interactionType dynamically defined after the function property name. Its available on props.interactions so we can rewrite the above snippet as:
function MyComponent({ interactions }) {
return (
<div>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
In the example above, the interactions.buttonClick function receives the click event as an argument and that is what you'll get in your main (next section).
While cleaner, it doesn't allow for the predefinedValue to be set, so if you need some particular value to be emitted by the associated stream in your main, you can either use interact or create an inline function:
// Assuming you have `id` available, e.g.
// when looping/mapping over the collection of items
<button
type="button"
className="delete-btn"
onClick={interact('delete', id)}
>
Click me!
</button>
// OR
<button
type="button"
className="delete-btn"
onClick={() => interactions.delete(id)}
>
Click me!
</button>
View events handling with interactions source
There's internal interactionsWrapper in react-cycle-connect and it provides the interactions source for our main function. The interactions source is of type InteractionsSource, which has the InteractionsSource.select() method, which we can use to obtain interaction streams:
function main(sources) {
const buttonClick$ = sources.interactions.select('buttonClick');
// Do something with a buttonClick$, e.g.
// map it to the click coordinates props
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interact, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
The streams are select()ed by the same interactionType string you provided to the interact() function on your view.
Similar to how interactions object with dynamic function-props is available on your view, InteractionsSource is a ES2015 Proxy and it maintains the dynamic streams right on itself (just don't use select as its reserved method name here here):
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interactions, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
Note: If you try to get the non-existing stream, it will still work, this thing is dynamic, so the stream will be created nevertheless but will just never emit values. Be careful and look out for typos! Using TypeScript is highly recommended to avoid typos here.
Upstream interactions with interactions sink (calling callbacks on props).
Now that we can handle the streams of view events and values from the children components, we would also need to somehow call the callbacks that are passed among our props.
First, nothing prevents from following the common React way and call those directly from the view:
function MyComponent({ onButtonClick }) {
return (
<button type="button" onClick={onButtonClick}>
Click me!
</button>
);
}
// Somewhere else:
doSomething = (event) => { console.log('clicked!'); };
...
<MyComponent onButtonClick={doSomething} />
This is fine if you don't need that value's stream in your dataflow. This approach bypasses your Cycle.js program's main completely and doesn't make it impure by otherwise possibly passing an effectful function as onButtonClick. There's even no need to cycleConnect() your component in the above example.
Keep in mind that if onButtonClick is optional there, you will also need to check whether that is defined before providing that as a callback (use PropTypes or TypeScript to mark it as either required or optional).
Oftentimes you will need to call some callback that lives on the props though, and this is what interactions sink provided by interactionsWrapper does:
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
const coords$ = buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
);
// We both update the `props` (to render it in this component)
// and pass upstream
return {
props: coords$,
interactions: xs.of({
onCoordsChange: coords$
})
};
}
function MyComponent({ interactions }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
handleCoordsChange = (coords) => {
console.log('New coordinates received: ', coords);
};
...
<ConnectedComponent onCoordsChange={handleCoordsChange} />
// The new coords will be rendered by the component
// and the console will show the "New coordinates received"
// with new coords too.
Once the appropriate stream emits a value, that callback is called. It will also check if the callback was passed in the first place to skip the call attempt if no callback with a given name is passed among the props.
Important: Note that interactions sink expects a stream of object whose property names correspond to callback names on props and property values are themselves streams too - whatever values those streams emit, will be passed as an argument to the callback.
So far so good, but we know that our interactionsWrapper is a wrapper and not a driver, so what if one passed the effectful function as a callback, e.g., handleCoordsChange passed as onCoordsChange above does a console.log() internally - that is a side effect, right?
Well, this is why the function is not invoked directly, or inside the wrapper (since the wrapper is the part of the pure land of our mains), its using the fnCallEffect driver internally. More on this below in a section about Side-effect control, just keep in mind that wrappers and main should be pure and react-cycle-connect follows that requirement.
React component lifecycle streams
You're probably not going to need React component lifecycle hooks often inside your main, but still - they are provided via special reactLifecycleWrapper. It provides the lifecycle source that is available on each cycleConnect()ed main sources.
The lifecycle events are provided as Observable streams and their names mostly mirror the React lifecycle methods, but shortened to remove some redundant component prefixes:
| React lifecycle method | lifecycle source property |
Stream value |
|---|---|---|
componentWillMount |
willMount$ |
null |
componentDidMount |
didMount$ |
null |
componentWillReceiveProps |
willReceiveProps$ |
nextProps |
componentWillUpdate |
willUpdate$ |
nextProps |
componentDidUpdate |
didUpdate$ |
prevProps |
componentWillUnmount |
willUnmount$ |
null |
componentDidCatch |
didCatch$ |
Error data |
The "error data" on the didCatch$ is an object of this shape:
{
error: Error;
errorInfo: ErrorInfo;
}
These are basically React componentDidCatch(error, errorInfo) arguments squashed into a single object.
Also, note that shouldComponentUpdate hook is not present there, since that is not really an event and affects how your component is rendered. react-cycle-connect would be providing its own custom internal shouldComponentUpdate instead for optimisation purposes and you could implement it on your wrapped component anyway.
Note: As well, no lifecycle events are passing anything related to React component
state, only takingpropsinto account instead. This is because thestateis meant to be managed differently when Cycle.js is in use. You can still use that for wrapped component but its out of play on the component generated bycycleConnect(...).
A quick example:
function main(sources) {
const willUnmount$ = { sources.lifecycle };
// Do something on unmounting, e.g. report that to a console
// via some hypothetical `logger` driver
return {
logger: willUnmount$.mapTo(`I'll be unmounted soon!`)
};
}
Side-effect control and fnCallEffect driver
Side-effect control is where Cycle.js really shines (not only implementation, it applies to the "cycle pattern" behind Cycle.js in the first place).
In order to keep the cycle part of cycleConnect()ed component pure, we need to make sure that any potentially effectful function is not run inside our main directly.
This is what fnCallEffect driver is responsible for. It allows to only describe what function you want to call and with what arguments, then delegate that to a driver which is supposed to run any impure functions outside of the app itself.
The way it works is very similar to that of HTTP driver from @cycle/http package where you don't actually run the request inside your main but just describe it and pass to HTTP driver to handle. fnCallEffect driver is just a bit more generic and operates on functions.
react-cycle-connect uses fnCallEfffect driver internally (to run effects for upstream interactions), but it is also available for you on any connected main as fnCallEffect sink automatically (i.e., without explicit configuration on a root cycle):
function log() {
console.log(`I'm effectful!`);
}
function saveToLocalStorage(message) {
localStorage.setItem('message', message);
}
function main(sources) {
const someCallbackFn$ = sources.props.pluck('someCallbackFunction');
return {
fnCallEffect: xs.merge(
xs.of({ fn: log }),
xs.of({ fn: saveToLocalStorage, args: ['some message to save'] }),
// Call the `props` callback function directly, this is how
// `interactions` sink works behind the scenes:
someCallbackFn$.map(fn => ({ fn }))
)
};
}
Isolation
Isolation is Cycle.js way of making the component's sources and sinks isolated from influence of other components.
Make sure to carefully read more about Cycle.js isolation here. It might take a bit to get used to an idea.
To reiterate: the concept of isolation is important for Cycle.js apps. Its a little bit different for react-cycle-connect because most things are scoped to a particular cycleConnect()ed component already, like components have "local" interactions, props and lifecycle sources and sinks and React components are also self-contained already.
There are though drivers/wrappers that will need the isolation feature, like HTTP driver or cycle-onionify wrapper (a state management solution for Cycle.js apps). So, isolation makes sense for shared drivers/wrappers, those you pass to @cycle/run function (by defining the drivers/wrappers options on root cycleConnect(...)) and that will be available across the entire tree of Cycle programs.
Note: The isolation works by scoping the source streams to particular component and a kind of unscoping of the sink streams before connecting to parent's sinks.
Also note, that each driver/wrapper decides on its own how its sources/sinks should be isolated.
In Cycle.js, you use the isolate() function from @cycle/isolate package directly, as you're just invoking the children main functions from your current component's main function:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// Do something with childSinks, combine somehow, etc
return {
...
};
}
isolate function has this simple signature:
isolate :: (MainFn, scope) => MainFn
It takes the main function and an optional scope and returns an isolated main function.
react-cycle-connect does also use the @cycle/isolate package internally, but you can't call isolate() directly on some childMain function. You'd never call one component's main from the other component's main directly with react-cycle-connect.
Naive explicit isolation (conceptually incorrect)
Just to illustrate the issue, there's an attempt to isolate the component's HTTP sources, so that HTTP requests initiated from that components are scoped to it (and are unavailable to siblings).
Since we know that isolate() returns a main function and cycleConnect() takes the main function as an argument, lets try to feed it an isolated version of it:
function main(sources) {
...
}
function MyComponent() {
...
}
const isolatedMain = isolate(main, { HTTP: 'myComponent' });
const ConnectedComponent = cycleConnect(isolatedMain)(MyComponent);
While this will won't raise any errors (because this is valid code after all), this approach brings a subtle issue.
This will isolate things for this particular components but not their children components, so in this case, children (connected childred React components) will have no myComponent isolation scope for HTTP channel.
This might seems like a no-issue, because hey, you can define different scopes for the children components right? Well, while this is technically possible, its conceptually incorrect and it will be completely broken in some cases, e.g., with cycle-onionify where the deeper you go - the "more filtered" source streams you get.
Important! The above note about it being "conceptually incorrect" refers to the way react-cycle-connect maintains the tree of Cycle.js programs internally. It tries to make the entire app structure look the same way as you'd manually write a Cycle.js app. So, when you isolate(childMain) at some level, then if inside your childMain you also call some grandChildMain(...) happens, then grandChildMain gets isolated sources no matter what.
isolate option for cycleConnect()
The first way to correctly isolate cycleConnect()ed component is to use the isolate option for cycleConnect() itself, so the above can be rewritten as:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main, {
isolate: { HTTP: 'myComponent' }
})(MyComponent);
This will work perfectly, with all children cycleConnect()ed components having myComponent namespace applied to HTTP sources/sinks in their main functions.
The isolate option accepts a string or the object and in that case its passed directly to @cycle/isolate package's isolate(main, scope) as a scope argument. So, check out the isolate function API reference.
react-cycle-connect also makes it accept a function that takes component props as an argument and should return the scope of string or object type that is then also passed to @cycle/isolate package's isolate(main, scope). This allows for some dynamic isolation scopes to be set based on the React component's initial props.
Important: Despite the
isolateis provided as an option tocycleConnect()when defining the connected component, the actual isolation will only happen when component instance is created. This is why its possible to dynamically resolve thescopebased on the componentpropsinreact-cycle-connect.
isolate prop
Despite the isolate option will work nicely for some cases, there's a pattern in how it is utilized in pure Cycle.js programs. Lets take a look at pure Cycle.js example again:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// ...
return {
...
};
}
The important part is that in Cycle.js app its not a child who decides how to isolate itself but scope is provided from the parent instead.
Most of the time, we want to maintain the same approach with react-cycle-connect. The advantage is that component in this case is totally agnostic from the provided scope, it doesn't even care whether its isolated or not at all - all it knows is that it expects certain sources and returns certain sinks. This makes the component reusable.
This is especially important with cycle-onionify, where you want to control where your child components "attach" to the state.
In other words, the parent decides how to compose children components, not the other way around, because otherwise the child would need to have an intimate knowledge of something that really belongs to parent and thus not being reusable and breaking encapsulation.
For this reason, every cycleConnected React component accepts an isolate prop, so you can use that just like any other prop:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
// Isolate everything
<ConnectedComponent isolate="myComponent" />
// Isolate HTTP only
<ConnectedComponent isolate={{ HTTP: 'myComponent' }} />
<ConnectedComponent isolate={{ HTTP: 'myComponent_2' }} />
In contrast with isolate option, the isolate prop can only accept string or object that is directly passed to isolate() function from @cycle/isolate, thus maintaining full parity with pure Cycle.js approach. Indeed, having a function that accepts initial props wouldn't make much sense because you're anyway providing props that could be different for different component instances when rendering that component.
<Isolator> helper component
Since isolation in react-cycle-connect applies to both "current" cycleConnect()ed component and all its cycleConnect()ed children, we might sometimes need to define some connected component to only be a common parent for some children, which must provide isolation but wouldn't contain any logic.
This is what a helper Isolator component for. It accepts the scope prop (that is directly passed as a scope argument to isolate(main, scope) function of @cycle/isolate):
<Isolator scope={{ HTTP: 'someNamespace' }}>
<ConnectedComponent someProps="1" />
<ConnectedComponent someProps="2" />
<ConnectedComponent someProps="3" />
</Isolator>
This assists the isolation composition at view level.
Extra: State management with cycle-onionify
cycle-onionify is the idiomatic Cycle.js fractal state management solution. It is tailored for Cycle.js apps but since react-cycle-connect is using the real Cycle.js, it stays fully compatible here as well.
Using cycle-onionify with react-cycle-connect is optional, since you might be using different lib (e.g., redux) for that.
To start, just read the onionify docs and install the package:
$ npm install --save cycle-onionify
and pass that with wrappers array to a root connected component (e.g., Root Cycle):
// RootCycle.js
import { run } from '@cycle/run';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
wrappers
})();
export default RootCycle;
Now every cycleConnect()ed component that is a child of the above-mentioned RootCycle in React component tree got the onion source and sink.
Important: Notice that you're all set - its purely a Cycle.js land. react-cycle-connect doesn't provide any additional functionality for cycle-onionify. Instead, it provides few helpers for isolation and working with collections on the React side.
All the exportables related to onionify reside in the extra package: react-cycle-connect/lib/extra/onionify. It is installed along with the main lib but is not require()d/imported by default, so won't be bundled (by Webpack, Rollup, etc) unless you're using it.
This extra sub-package contains few things:
<StateIsolator>helper component<Collection>helper componentmakeFilteredListLens()helper factory function
Read ahead for details on each.
In cycle-onionify, the state/reducers layer composition is done via isolation, so the first thing here is <StateIsolator>:
<StateIsolator> helper component
This is pretty much like <Isolator> but is here to specifically isolate onion sources/sinks only.
So, the:
import { StateIsolator } from 'react-cycle-connect/lib/extra/onionify';
// ...
<StateIsolator lens="someStateProperty">
<NestedComponent />
</StateIsolator>
is somewhat a convenient shortcut to:
import { Isolator } from 'react-cycle-connect';
// ...
<Isolator scope={{ onion: 'someStateProperty', '*': null }}>
<NestedComponent />
</Isolator>
Notice how StateIsolator's prop is explicitly named lens. It just implies that oftentimes you'll be using "lens" to isolate the state. Consider a string just a shortcut to a more elaborate lens object.
In the example above, the <NestedComponent>'s main function sources.onion.state$ will represent only that "someStateProperty" piece of state. Pretty much how you'd isolate() the onion channel in an idiomatic Cycle.js app.
There's also a channelName property, so that you can change the name of the source/sink inside your main to something else. Thats to be on par with cycle-onionify features, where you can also change that name.
Handling collection state manually
Managing collections of components in pure Cycle.js with onionify is a kind of a recognized challenge. This is why cycle-onionify ended up providing makeCollection() helper function - see their docs for more detail.
While react-cycle-connect also has a <Collection> helper component that is very similar to cycle-onionify makeCollection() function, lets first try to pretend we don't have it and imagine how'd we handle the state without it.
Note: Nothing prevents you from skipping right to a description of how
<Collection>works, but it might be useful to understand the rationale behind it, so reading this section is recommended.
Handling collections is different with React than in Cycle.js, e.g., you can always just render React components in a loop or .map() an array of data to an array of components and React will render that.
When you want to render some Array data from your state, its easy without any helpers:
/*
Assume you have this value on your `List` component `stateFork
- updated to more recent version of cycle, including @cycle/state
- updated to React 16.9 api, including new lifecycles and context api
- merged onionify separate lib so import is __INLINE_CODE_0__
- added tests
- published at @iilab/react-cycle-connect
react-cycle-connect
An experimental library that allows using Cycle.js as a reactive dataflow framework for React.
Written in TypeScript.
This little thing allows to define your app's view layer using regular React components, but implement the actual logic behind it using pure functional reactive approach, taking advantage of Cycle.js' Model-View-Intent pattern and its exceptional side effect control.
This isn't a custom implementation of Cycle pattern for __INLINE_CODE_1__ but a link to real Cycle.js instead, so any Cycle.js drivers and wrappers can be applied (e.g., __INLINE_CODE_2__ for state management). Also, for this reason, some knowledge about how __INLINE_CODE_3__ works is assumed.
Note: This is an experimental lib and currently works with __INLINE_CODE_4__ and __INLINE_CODE_5__ only. If all goes good, the support for __INLINE_CODE_6__/__INLINE_CODE_7__ (instead of __INLINE_CODE_8__) and for __INLINE_CODE_9__/__INLINE_CODE_10__ (instead of __INLINE_CODE_11__) is anticipated. Nothing prevents from having __INLINE_CODE_12__ covered too.
UPDATE (MAR 2018): Some internals overhaul is pending and it's currently waiting for Cycle.js to settle on hybrid push-pull reactivity and have internals rewritten to use Callbags instead of __INLINE_CODE_13__. So, __INLINE_CODE_14__ is going to be using Callbags by default too.
Any feedback is highly appreciated!
Table of Contents
- react-cycle-connect
- Table of Contents
- Shup up and show me the code
- Installation
- Motivation
- How it works (architecture)
- Guide
- API reference
- Alternatives / Competitors
- FAQ
- Roadmap
Shup up and show me the code
Cycle.js's counter example ported over:
import React from 'react';
import ReactDOM from 'react-dom';
import xs from 'xstream';
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
// Your Cycle.js `main` function
function main(sources) {
const action$ = xs.merge(
sources.interactions.decrement.map(ev => -1),
sources.interactions.increment.map(ev => +1)
);
const count$ = action$.fold((acc, x) => acc + x, 0);
return {
props: count$.map(count => ({ count }))
};
}
// Your React component
function App({ interactions, count }) {
return (
<div>
<button onClick={interactions.decrement}>Decrement</button>
<button onClick={interactions.increment}>Increment</button>
<p>Counter: {count}</p>
</div>
);
}
// Some connection magic
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
// Render normally as it's a regular React app
ReactDOM.render(
<ConnectedApp />,
document.getElementById('root')
);
Also - there's official TodoMVC example with __INLINE_CODE_33__ for state management.
Installation
Using npm:
$ npm install --save react-cycle-connect
Using yarn:
$ yarn add react-cycle-connect
Motivation
The motivation to create this lib was based off the need to retain the React-style composability of the view layer using regular React components, reuse existing React components, make it (the lib) easily/incrementally adoptable in an ongoing React project, but also take advantage of the Cycle.js' MVI pattern and side effect control.
At the same time, while the most valuable thing from __INLINE_CODE_34__ is the pattern itself, there are numerous available libs/plugins for Cycle.js to take advantage of, letting us take the best of both worlds.
This way, you can still use anything from React stack (e.g., __INLINE_CODE_35__ to compose your routed app screens), but describe the interactions as a dataflow, with pure functions and reactive streams. Most of React components are going to be "dumb" (and also stateless functional aka SFC) since all the backing logic and cross-component communication would be handled by a tree of __INLINE_CODE_36__ programs.
Why not just use Cycle.js alone?
That is what I'd recommend considering in the first place. Its fast, its cool, the __INLINE_CODE_37__ (virtual DOM rendering engine behind Cycle's DOM driver) is goddamn fast and reliable. If you don't intend to use __INLINE_CODE_38__ components in any way, don't even get bothered. The __INLINE_CODE_39__ is more about "introducing Cycle.js to __INLINE_CODE_40__", not the other way around.
Is it a "Cycle.js in React" or "React in Cycle.js"?
More of a "Cycle.js in React". The app is bootstrapped by rendering a React component to the DOM, view is composed the React way. The root Cycle.js program is being __INLINE_CODE_41__ by rendering a root __INLINE_CODE_42__ed component. Though, it doesn't prescribe how you write your Cycle.js programs, and aside from the DOM generation part, its just Cycle.js all the way.
How it works (architecture)
Every __INLINE_CODE_43__ed component got a real Cycle.js program "attached" to it. The boundary between the two is __INLINE_CODE_44__. From your Cycle.js program standpoint, __INLINE_CODE_45__ is a view. Cycle.js program can update __INLINE_CODE_46__ that React component will then render.
A bunch of nested React components in your app represent a "tree of React components". __INLINE_CODE_47__ maintains a "tree of Cycle.js programs" that map to __INLINE_CODE_48__ed components and takes advantage of React's context feature to provide links between the nodes. Each connected component provides scoped links to the children nodes .
New "Cycle nodes" are attached lazily, upon associated React component initialization (this is where React controls how Cycle programs are run). The above-mentioned "link" is just an object on React __INLINE_CODE_49__ that brings couple properties:
- __INLINE_CODE_50__ for the child connected component's __INLINE_CODE_51__ function; and
- __INLINE_CODE_52__ to subscribe to that component's __INLINE_CODE_53__ function __INLINE_CODE_54__.
This is where something unusual for the Cycle.js happens - we subscribe to and unsubscribe from connected component's __INLINE_CODE_55__ function __INLINE_CODE_56__ based on React component's lifetime hooks. In Cycle.js, you normally just run the app at startup and let it run as an ongoing process. But this is where __INLINE_CODE_57__ performs the sync between the two worlds.
Overall, aside from using React __INLINE_CODE_58__ (which is just an implementation detail) and some intense wrapping on the __INLINE_CODE_59__ side to make laziness possible, __INLINE_CODE_60__ tries to represent the entire tree of Cycle.js programs as close as possible to what you'd end up with by writing idiomatic Cycle.js with explicit calling of nested component's __INLINE_CODE_61__ functions from the parent component __INLINE_CODE_62__ function. The result of children components is always __INLINE_CODE_63__d.
Aside from passing the __INLINE_CODE_64__ to children components, __INLINE_CODE_65__ also explicitly applies some internal wrappers to integrate with React component: __INLINE_CODE_66__, __INLINE_CODE_67__ and __INLINE_CODE_68__, that are scoped to a connected component's Cycle.js program only. It also explicitly runs the internal __INLINE_CODE_69__ driver for the __INLINE_CODE_70__ nodes by default - this is necessary for the __INLINE_CODE_71__ sink to work (because its supposed to run callbacks passed via __INLINE_CODE_72__ and that might be an effectful function).
Note that the entire internal "tree" of Cycle.js programs is maintained pure, including internal component-scoped wrappers, just as you would do with regular Cycle.js program.
Differences from idiomatic Cycle.js usage
Note: there's a difference in how'd you write the __INLINE_CODE_73__/__INLINE_CODE_74__ app vs pure __INLINE_CODE_75__ app. There's no misuse of Cycle.js per se, we just don't use it for rendering with __INLINE_CODE_76__ - and that applies certain implications to the approach.
__INLINE_CODE_77__ and __INLINE_CODE_78__, while both allow to create user interfaces, imply different approaches to the app architecture. The most important part here is that they are conceptually different when it comes to generating the VDOM.
In order to combine the two approaches nicely, there are certain deviations from how'd you write the __INLINE_CODE_79__-only app to allow for more optimal React DX.
The main difference from a pure __INLINE_CODE_80__ way is that its not a Cycle program that technically generates VDOM - instead, there's React component to represent a piece of UI. __INLINE_CODE_81__ approach to view/__INLINE_CODE_82__ event handling is rather purist - view only defines the markup and nothing else (i.e., user interaction/event handlers are defined inside Cycle program itself and not as some view component __INLINE_CODE_83__/etc attribute). That is not a crazy random opinion but instead a very well thought and weighted out decision by Cycle.js team. __INLINE_CODE_84__ takes a different approach, which isn't that strict and is more in line with React, with callbacks amongst __INLINE_CODE_85__ and all that stuff. It enables the events/component callbacks handling via interactions.
The view composition is obviously completely different. In __INLINE_CODE_86__ you run each component's __INLINE_CODE_87__ function that (possibly) generates a VDOM node and then explicitly combine the pieces of markup into a single VDOM stream that is then given to a DOM driver. With __INLINE_CODE_88__ you compose the view layer with React components normally. You can just connect those to Cycle.js program to "outsource" the actual logic to it.
Guide
This assumes the preliminary knowledge of both __INLINE_CODE_89__ and __INLINE_CODE_90__ (and __INLINE_CODE_91__). It doesn't provide much of the insight on how these work, but is instead focused on how __INLINE_CODE_92__ combines the two.
Basics
The result of __INLINE_CODE_93__ call is a HOC that optionally takes a React component (similar to that __INLINE_CODE_94__ function of __INLINE_CODE_95__) and returns a component that is augmented with backing Cycle.js program. This Cycle.js program is internally wrapped into a "Cycle Node" that, when initialized, is connected to a tree of Cycle.js programs. "Cycle Nodes" of every __INLINE_CODE_96__ed components are linked together via React __INLINE_CODE_97__.
Note, though, that there are no direct references from one component's Cycle program to another component's Cycle program, i.e., you're not invoking one __INLINE_CODE_98__ function from the other (this is perfectly possible though, but since we're not using __INLINE_CODE_99__ here, it would only handle non-DOM stuff). Instead, __INLINE_CODE_100__ handles the linking behind the scenes, so the "sibling" Cycle programs are fed with the same __INLINE_CODE_101__ and subscribe their __INLINE_CODE_102__ to the same __INLINE_CODE_103__.
There's essential concept of isolation in Cycle.js, so every __INLINE_CODE_104__ed component accepts an __INLINE_CODE_105__ prop, that is directly passed to __INLINE_CODE_106__ function from __INLINE_CODE_107__ package. Read more about Cycle.js isolation here.
Root Cycle
Every __INLINE_CODE_108__ed component provides a __INLINE_CODE_109__ object to its children through React __INLINE_CODE_110__ and also expects that __INLINE_CODE_111__ from some parent component (not necessarily immediate parent).
That link object has the following shape:
{
// Sources to provide to our Cycle.js program's `main` function as an argument
sources: { ... },
// sinkProxies to subscribe to our Cycle program's `main` function sinks
sinkProxies: { ... }
}
You shouldn't be working with that directly but its important that something needs to provide the top-level __INLINE_CODE_112__ and __INLINE_CODE_113__ in the first place. This is what __INLINE_CODE_114__ option of cycle connect for.
Also, the __INLINE_CODE_115__ed components' programs (__INLINE_CODE_116__ functions) are lazily attached to already running upper-level Cycle.js program, so the __INLINE_CODE_117__ program has to be __INLINE_CODE_118__.
So, root __INLINE_CODE_119__, aside from __INLINE_CODE_120__ option, also needs __INLINE_CODE_121__ defined, so that it knows how to run your top-level Cycle.js program. This would normally be just a __INLINE_CODE_122__ function from __INLINE_CODE_123__ package (or __INLINE_CODE_124__, or __INLINE_CODE_125__, depending on the reactive streams library in use).
You could just __INLINE_CODE_126__ your top-level __INLINE_CODE_127__ component like we did in the first example:
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
...
function main(sources) {
...
}
function App() {
return (
// Some JSX
...
);
}
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
Though this will work, its advisable to create a specialized __INLINE_CODE_128__ component just for this purpose:
// RootCycle.js
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
function main(sources) {
...
}
export const RootCycle = cycleConnect(main, {
root: true,
runFn: run
})(); // No component to wrap? Its okay, read ahead
export default RootCycle;
The top-level Cycle sources are actually __INLINE_CODE_129__ and __INLINE_CODE_130__, so root Cycle is also in charge of providing these __INLINE_CODE_131__ and __INLINE_CODE_132__ to any Cycle.js program down the tree, as __INLINE_CODE_133__.
__INLINE_CODE_134__ option is the object with keys defining properties of the __INLINE_CODE_135__ object, identical to how you'd do that in a regular Cycle.js program.
__INLINE_CODE_136__ option accepts an array of wrappers (order might matter there, so its a user-land option).
Also, the __INLINE_CODE_137__ function is optional (because you might need a __INLINE_CODE_138__ to be a __INLINE_CODE_139__ or for __INLINE_CODE_140__ only), so you can skip it, unless you have some logic to implement there.
Note that the __INLINE_CODE_141__ can be passed to any __INLINE_CODE_142__, even non-root, but __INLINE_CODE_143__ option is only valid for __INLINE_CODE_144__ Cycle and is ignored otherwise.
// RootCycle.js
import { run } from '@cycle/run';
import { makeHTTPDriver } from '@cycle/http';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const drivers = { HTTP: makeHTTPDriver() };
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
export default RootCycle;
__INLINE_CODE_145__ component is basically in charge of bootstrapping the "Cycle" part of your app here.
Then just wrap your __INLINE_CODE_146__ with it and e.g., render with __INLINE_CODE_147__:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import RootCycle from './RootCycle';
ReactDOM.render(
(
<RootCycle>
<App />
</RootCycle>
),
document.getElementById('root')
);
__INLINE_CODE_148__ option
You might have noticed in axamples above that we're not providing any component to a __INLINE_CODE_149__ HOC, leaving empty braces instead:
const ConnectedComponent = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
Well, that is not a mistake - the wrapped component is optional. When a "component to wrap" is provided to __INLINE_CODE_150__, then if we render the connected component, the __INLINE_CODE_151__ will also render __INLINE_CODE_152__ internally.
Thats all good and works (and what you'll be using most of the time anyway), but sometimes you just want to keep your React Element tree a bit cleaner when the wrapped component would be a kind of noop anyway (just forwarding __INLINE_CODE_153__ and nothing else), or component is some technical/lower-level like __INLINE_CODE_154__, or have some special rendering behavior.
So, we want to avoid seeing this in React Developer Tools:
<cycleConnect(SomeComponent)>
<SomeComponent>
...
</SomeComponent>
</cycleConnect(SomeComponent)>
You can use __INLINE_CODE_155__ option for that. It accepts the function with this signature:
(props) => ReactNode | null
If no component is provided for wrapping, nor __INLINE_CODE_156__ option is defined, the __INLINE_CODE_157__ option defaults to __INLINE_CODE_158__ function, so its either forwarding __INLINE_CODE_159__ if any or renders nothing.
If both __INLINE_CODE_160__ option is provided and component-to-wrap is passed, then __INLINE_CODE_161__ takes the precedence.
Note: Why not use the __INLINE_CODE_162__ option in place of wrapped component all the time? Well, its technically possible, espeically if your React component is a stateless functional component anyway.
But regular HOC wrapping is just common technique in React world and is nicely composable when you have other HOCs than __INLINE_CODE_163__.
And is cleaner too - you might be prototyping your app as a set of __INLINE_CODE_164__ components first, without even involving __INLINE_CODE_165__, then "connect" them all at once, or introduce __INLINE_CODE_166__ incrementally. Whatever :)
__INLINE_CODE_167__ option
Your component's __INLINE_CODE_168__ is by default automatically assigned to the following (depending on whether the wrapped component is provided, or __INLINE_CODE_169__ option, or neither:
- __INLINE_CODE_170__ when WrappedComponent is passed
- __INLINE_CODE_171__ when __INLINE_CODE_172__ prop is defined (and is truthy)
- __INLINE_CODE_173__ otherwise
You can override that like with any regular React component:
const ConnectedComponent = cycleConnect({
...
)(Component);
ConnectedComponent.displayName = 'ConnectedComponent';
But there's also a __INLINE_CODE_174__ option to set the generated (connected) component __INLINE_CODE_175__, which looks a bit cleaner:
const ConnectedComponent = cycleConnect({
...
displayName: 'ConnectedComponent'
)(Component);
Inside the Cycle
__INLINE_CODE_176__ really assumes that you know how to use Cycle.js. Its really a very simple yet very elegant pattern, so make sure to familiarize yourself with it.
Quick recap on what Cycle.js __INLINE_CODE_177__ function is. Its basically a pure function with the signature:
main :: (Sources) => Sinks
It accepts __INLINE_CODE_178__ as an argument and returns __INLINE_CODE_179__ and describes a dataflow using composition of reactive streams and their transformations with pure functions.
Please note that the __INLINE_CODE_180__ function is only run once per __INLINE_CODE_181__ component lifecycle. It doesn't run again on rerendering or something. This is because the actual call to __INLINE_CODE_182__ is to just build up a dataflow, not to get some rendering data.
The data flows through reactive streams, from __INLINE_CODE_183__ to __INLINE_CODE_184__ of the __INLINE_CODE_185__, until its __INLINE_CODE_186__ are unsubscribed from parent Cycle program's __INLINE_CODE_187__ (this happens when React component is unmounted).
Drivers
The __INLINE_CODE_188__ is supposed to run side effects for a Cycle.js program. Its outside the program itself, supposed to be impure and connects your Cycle app with the "outer world". This is an essential concept and is described in a great detail in Cycle.js docs on drivers.
There are no differences from idiomatic Cycle.js in how __INLINE_CODE_189__ handles __INLINE_CODE_190__ - it just hands it over to __INLINE_CODE_191__ directly. There is only one __INLINE_CODE_192__ driver (an __INLINE_CODE_193__ driver) that is always added internally by the __INLINE_CODE_194__ed component - it allows to invoke functions outside the __INLINE_CODE_195__ that is to be maintained pure.
Wrappers
The __INLINE_CODE_196__ is a rather simple thing - its just a function that takes __INLINE_CODE_197__ function that wraps it with the other __INLINE_CODE_198__ function that might in turn add some logic in between:
function someWrapper(originalMain) {
return wrappedMain(sources) {
// Can do something here, like transforming `sources` somehow
// ...
// Invoke the original `main` internally to obtain its sinks
const sinks = originalMain(sources);
// Can do something to `originalMain`s sinks here
// ...
return {
...sinks,
someOtherSink: xs.of({ ... }) // Can add a new sink
};
}
}
It is important that the wrapping __INLINE_CODE_199__ is maintained pure, so that your entire program is pure. If you need some effectful logic to run in a wrapping __INLINE_CODE_200__, you should consider using driver for that. Wrappers, just like any regular __INLINE_CODE_201__ function, can internally use __INLINE_CODE_202__ that are created from __INLINE_CODE_203__.
Wrappers can also add arbitrary __INLINE_CODE_204__ and could expect certain __INLINE_CODE_205__ to be returned from the Cycle program, depending on particular wrapper implementation.
Wrappers are usually created to encapsulate some non-business-logic stuff, you'd be normally describe anything related to the app itself in the "original" __INLINE_CODE_206__ function.
Most of __INLINE_CODE_207__ (aside from __INLINE_CODE_208__ HOC itself) is implemented with wrappers, that are created for each __INLINE_CODE_209__ed component automatically:
- __INLINE_CODE_210__ (to mess around with React component props)
- __INLINE_CODE_211__ (a gateway to React component lifecycle streams)
- __INLINE_CODE_212__ (to route the view events back from your wrapped React component aka view, and pass the events upstream, to parent components)
Sources
This is a Cycle.js program's input. __INLINE_CODE_213__ function shouldn't care whether something on the __INLINE_CODE_214__ is a __INLINE_CODE_215__ or some custom thing provided by one of the wrappers.
For __INLINE_CODE_216__ connected component, the __INLINE_CODE_217__ are __INLINE_CODE_218__ directly plus whatever __INLINE_CODE_219__ add to them.
Sinks
This is a Cycle.js program's output.
__INLINE_CODE_220__ invokes your __INLINE_CODE_221__ed component's __INLINE_CODE_222__ function, takes its __INLINE_CODE_223__ and subscribes the __INLINE_CODE_224__ passed from the parent component to these __INLINE_CODE_225__. Its how __INLINE_CODE_226__ is "attaching" the new component to a tree of Cycle programs.
If there's no parent component (thus, no __INLINE_CODE_227__) and your component is a "Root Cycle", then the sinks are handled to __INLINE_CODE_228__ to "run" as with a regular Cycle.js app.
React __INLINE_CODE_229__
The __INLINE_CODE_230__ed __INLINE_CODE_231__ component is essentially a view for the associated Cycle.js __INLINE_CODE_232__ function. And __INLINE_CODE_233__ encourages the view to be a "dumb" stateless function that takes __INLINE_CODE_234__ as an argument ad returns something of __INLINE_CODE_235__ type (i.e., __INLINE_CODE_236__s, __INLINE_CODE_237__, etc, whatever __INLINE_CODE_238__ component renders).
Note: The wrapped __INLINE_CODE_239__ shouldn't necessarily be a stateless functional component (SFC), you could have any __INLINE_CODE_240__ component wrapped, but its advisable to use functions for the most part as the whole point of __INLINE_CODE_241__ is to use __INLINE_CODE_242__ for anything that is not a view.
So, the __INLINE_CODE_243__ is the only communication channel from our Cycle.js part (the connected component's __INLINE_CODE_244__ function) to connected __INLINE_CODE_245__ component itself. We therefore need to pass the props from our Cycle program to the view (a __INLINE_CODE_246__ component) somehow.
This is where __INLINE_CODE_247__ comes into play and provides __INLINE_CODE_248__ source and sink for a __INLINE_CODE_249__ function. Its local to a component, so no other components will share the same __INLINE_CODE_250__ source or sink.
__INLINE_CODE_251__ sink
The __INLINE_CODE_252__ sink on __INLINE_CODE_253__ed is there to pass the props to the view:
function main(sources) {
return {
props: xs.of({ greetable: 'World' })
};
}
// Use the passed `greetable` prop
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
Remember, that the value you map to __INLINE_CODE_254__ sink should be an object of new props and that updated props will be merged with existing component __INLINE_CODE_255__ like:
const updatedProps = Object.assign({}, prevProps, newProps);
This means that you can easily overwrite any props you want and leave the others intact.
Also, note above that we're passing the __INLINE_CODE_256__ through an __INLINE_CODE_257__ stream and that might be synchronous (like in our example with a stream of static data that is sent immediately) but could also be asynchronous. In that case the value of some prop might be not yet available on the first render:
function main(sources) {
return {
props: xs.of({ greetable: 'World' }).compose(delay(3000))
};
}
// Trying to use the passed `greetable` prop
// But whooops, no `greetable` yet :(
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
So either provide a default value for that async prop's __INLINE_CODE_258__ or perform additional checks in the view, when rendering.
Note: When messing with the __INLINE_CODE_259__ in your __INLINE_CODE_260__, remember that any props that are passed from outside as an input to a __INLINE_CODE_261__ component, are also there (including callback functions, if there are any) and are also merged upon updates from outside (on __INLINE_CODE_262__).
Lets summarize: the __INLINE_CODE_263__ from the __INLINE_CODE_264__ sink are passed from the __INLINE_CODE_265__ function to the view (__INLINE_CODE_266__ component).
__INLINE_CODE_267__ source
Not only you'll need to provide the props to the view for rendering, but oftentimes you'd need your __INLINE_CODE_268__ to take part in transformations/calculations in the __INLINE_CODE_269__ function itself. For that reason, __INLINE_CODE_270__ provides the __INLINE_CODE_271__ source as well.
It allows you to have an __INLINE_CODE_272__ stream of your component's input __INLINE_CODE_273__. Note that the __INLINE_CODE_274__ is of type __INLINE_CODE_275__ and to take the actual stream of props, use its __INLINE_CODE_276__ attribute:
function main(sources) {
const name$ = sources.props.props$.map(props => props.name);
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
function MyComponent(props) {
return (
<div>
Hello, {props.name}! Your father is Mr. {props.father}.
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
...
// Somewhere else:
<MyConnectedComponent name="Luke Skywalker" father="Darth Vader" />
// renders:
//
// <div>
// Hello, LUKE SKYWALKER! Your father is Mr. Darth Vader.
// </div>
The new value on __INLINE_CODE_277__ is emitted every time __INLINE_CODE_278__ change. This could be overkill for the most cases, as you would only need just few props for your dataflow.
This is what __INLINE_CODE_279__ method for, it accepts the name of the nested prop (will return __INLINE_CODE_280__ if the stream value is not an object) and returns a stream of that prop only that is only got new value once this particular prop changes:
function main(sources) {
const name$ = sources.props.pluck('name');
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
The __INLINE_CODE_281__ed stream also performs shallow comparison and does __INLINE_CODE_282__ behind the scenes, so identical values don't cause a new value emitted on an __INLINE_CODE_283__.
Note that you don't need to always pull all props from your __INLINE_CODE_284__ function's __INLINE_CODE_285__ just for the sake of it to just forward to the __INLINE_CODE_286__ sink directly, you better only pick those you need in a dataflow with __INLINE_CODE_287__. The __INLINE_CODE_288__ passed from outside will be available on the view normally. You can overwrite them from inside your __INLINE_CODE_289__ though.
There's also __INLINE_CODE_290__ method that also accepts a nested prop name. The difference is that the result is not an __INLINE_CODE_291__ stream of prop values but the __INLINE_CODE_292__ instance itself that is scoped to that piece of __INLINE_CODE_293__:
function main(sources) {
const name$ = sources.props.pluck('name'); // Stream of strings
const nameSource = sources.props.select('someNestedObject'); // Scoped ReactPropsSource
const someNestedObjProperty$ = nameSource.pluck('someNestedObjectProperty');
...
}
This is useful when you want to __INLINE_CODE_294__ a stream of some sub-prop from deeper nested objects.
Note: Taking deep property stream this way isn't really nice currently as requires quite a bit of boilerplate - this part of API is already being thought out further and reconsidered.
Interactions
So far so good, but rendering the __INLINE_CODE_295__ isn't the only exciting thing we want to do with __INLINE_CODE_296__, right? We also want to handle events coming from the view(e.g., the DOM).
The way __INLINE_CODE_297__ handles events and nested components' callbacks is the biggest deviation from an idiomatic Cycle.js app using __INLINE_CODE_298__ driver. It refrains from a holistic "no handlers in the view" approach for a few reasons:
- We need something more in line with __INLINE_CODE_299__ as we're using React here
- We're not always handling DOM events but also need to handle some children components data/events passed back. We can't pass streams like in regular Cycle.js app, in __INLINE_CODE_300__ we do it via callbacks that are provided amongst children components' __INLINE_CODE_301__ and we need __INLINE_CODE_302__ed components to be compatible __INLINE_CODE_303__ components at all levels.
This is what "interactions" feature of __INLINE_CODE_304__ is for. It provides the unified interface for parent-child components communication and event handling that is aligned with a regular event/callback handling model on __INLINE_CODE_305__ side but also allows to access those events in form of __INLINE_CODE_306__ streams inside the __INLINE_CODE_307__ function.
Events triggering by the view
Every __INLINE_CODE_308__ component has some additional __INLINE_CODE_309__-specific props injected, so they are available on your view.
These are __INLINE_CODE_310__ which is a function that takes the __INLINE_CODE_311__ (a string) and optional __INLINE_CODE_312__ (of any type) and returns a function that will be a callback for your event or some child component prop function.
So, the signature is:
(interactionType, predefinedValue?) => (value) => void
Its supposed to be called imperatively in your view and is available as __INLINE_CODE_313__:
function MyComponent({ interact }) {
return (
<div>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
For the sake of being cleaner (and for better TypeScript support), __INLINE_CODE_314__ also leverages the ES2015 __INLINE_CODE_315__ feature to provide the object with dynamic properties, each representing an __INLINE_CODE_316__ with __INLINE_CODE_317__ dynamically defined after the function property name. Its available on __INLINE_CODE_318__ so we can rewrite the above snippet as:
function MyComponent({ interactions }) {
return (
<div>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
In the example above, the __INLINE_CODE_319__ function receives the click __INLINE_CODE_320__ as an argument and that is what you'll get in your __INLINE_CODE_321__ (next section).
While cleaner, it doesn't allow for the __INLINE_CODE_322__ to be set, so if you need some particular value to be emitted by the associated stream in your __INLINE_CODE_323__, you can either use __INLINE_CODE_324__ or create an inline function:
// Assuming you have `id` available, e.g.
// when looping/mapping over the collection of items
<button
type="button"
className="delete-btn"
onClick={interact('delete', id)}
>
Click me!
</button>
// OR
<button
type="button"
className="delete-btn"
onClick={() => interactions.delete(id)}
>
Click me!
</button>
View events handling with __INLINE_CODE_325__ source
There's internal __INLINE_CODE_326__ in __INLINE_CODE_327__ and it provides the __INLINE_CODE_328__ source for our __INLINE_CODE_329__ function. The __INLINE_CODE_330__ source is of type __INLINE_CODE_331__, which has the __INLINE_CODE_332__ method, which we can use to obtain interaction streams:
function main(sources) {
const buttonClick$ = sources.interactions.select('buttonClick');
// Do something with a buttonClick$, e.g.
// map it to the click coordinates props
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interact, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
The streams are __INLINE_CODE_333__ed by the same __INLINE_CODE_334__ string you provided to the __INLINE_CODE_335__ function on your view.
Similar to how __INLINE_CODE_336__ object with dynamic function-props is available on your view, __INLINE_CODE_337__ is a ES2015 __INLINE_CODE_338__ and it maintains the dynamic streams right on itself (just don't use __INLINE_CODE_339__ as its reserved method name here here):
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interactions, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
Note: If you try to get the non-existing stream, it will still work, this thing is dynamic, so the stream will be created nevertheless but will just never emit values. Be careful and look out for typos! Using TypeScript is highly recommended to avoid typos here.
Upstream interactions with __INLINE_CODE_340__ sink (calling callbacks on __INLINE_CODE_341__).
Now that we can handle the streams of view events and values from the children components, we would also need to somehow call the callbacks that are passed among our __INLINE_CODE_342__.
First, nothing prevents from following the common React way and call those directly from the view:
function MyComponent({ onButtonClick }) {
return (
<button type="button" onClick={onButtonClick}>
Click me!
</button>
);
}
// Somewhere else:
doSomething = (event) => { console.log('clicked!'); };
...
<MyComponent onButtonClick={doSomething} />
This is fine if you don't need that value's stream in your dataflow. This approach bypasses your Cycle.js program's __INLINE_CODE_343__ completely and doesn't make it impure by otherwise possibly passing an effectful function as __INLINE_CODE_344__. There's even no need to __INLINE_CODE_345__ your component in the above example.
Keep in mind that if __INLINE_CODE_346__ is optional there, you will also need to check whether that is defined before providing that as a callback (use __INLINE_CODE_347__ or TypeScript to mark it as either required or optional).
Oftentimes you will need to call some callback that lives on the __INLINE_CODE_348__ though, and this is what __INLINE_CODE_349__ sink provided by __INLINE_CODE_350__ does:
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
const coords$ = buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
);
// We both update the `props` (to render it in this component)
// and pass upstream
return {
props: coords$,
interactions: xs.of({
onCoordsChange: coords$
})
};
}
function MyComponent({ interactions }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
handleCoordsChange = (coords) => {
console.log('New coordinates received: ', coords);
};
...
<ConnectedComponent onCoordsChange={handleCoordsChange} />
// The new coords will be rendered by the component
// and the console will show the "New coordinates received"
// with new coords too.
Once the appropriate stream emits a value, that callback is called. It will also check if the callback was passed in the first place to skip the call attempt if no callback with a given name is passed among the __INLINE_CODE_351__.
Important: Note that __INLINE_CODE_352__ sink expects a stream of object whose property names correspond to callback names on __INLINE_CODE_353__ and property values are themselves streams too - whatever values those streams emit, will be passed as an argument to the callback.
So far so good, but we know that our __INLINE_CODE_354__ is a wrapper and not a driver, so what if one passed the effectful function as a callback, e.g., __INLINE_CODE_355__ passed as __INLINE_CODE_356__ above does a __INLINE_CODE_357__ internally - that is a side effect, right?
Well, this is why the function is not invoked directly, or inside the wrapper (since the wrapper is the part of the pure land of our __INLINE_CODE_358__s), its using the __INLINE_CODE_359__ driver internally. More on this below in a section about Side-effect control, just keep in mind that __INLINE_CODE_360__ and __INLINE_CODE_361__ should be pure and __INLINE_CODE_362__ follows that requirement.
React component lifecycle streams
You're probably not going to need __INLINE_CODE_363__ component lifecycle hooks often inside your __INLINE_CODE_364__, but still - they are provided via special __INLINE_CODE_365__. It provides the __INLINE_CODE_366__ source that is available on each __INLINE_CODE_367__ed __INLINE_CODE_368__ sources.
The lifecycle events are provided as __INLINE_CODE_369__ streams and their names mostly mirror the React lifecycle methods, but shortened to remove some redundant __INLINE_CODE_370__ prefixes:
React lifecycle method
__INLINE_CODE_371__ source property
Stream value
__INLINE_CODE_372__
__INLINE_CODE_373__
__INLINE_CODE_374__
__INLINE_CODE_375__
__INLINE_CODE_376__
__INLINE_CODE_377__
__INLINE_CODE_378__
__INLINE_CODE_379__
__INLINE_CODE_380__
__INLINE_CODE_381__
__INLINE_CODE_382__
__INLINE_CODE_383__
__INLINE_CODE_384__
__INLINE_CODE_385__
__INLINE_CODE_386__
__INLINE_CODE_387__
__INLINE_CODE_388__
__INLINE_CODE_389__
__INLINE_CODE_390__
__INLINE_CODE_391__
Error data
The "error data" on the __INLINE_CODE_392__ is an object of this shape:
{
error: Error;
errorInfo: ErrorInfo;
}
These are basically React __INLINE_CODE_393__ arguments squashed into a single object.
Also, note that __INLINE_CODE_394__ hook is not present there, since that is not really an event and affects how your component is rendered. __INLINE_CODE_395__ would be providing its own custom internal __INLINE_CODE_396__ instead for optimisation purposes and you could implement it on your wrapped component anyway.
Note: As well, no lifecycle events are passing anything related to React component __INLINE_CODE_397__, only taking __INLINE_CODE_398__ into account instead. This is because the __INLINE_CODE_399__ is meant to be managed differently when Cycle.js is in use. You can still use that for wrapped component but its out of play on the component generated by __INLINE_CODE_400__.
A quick example:
function main(sources) {
const willUnmount$ = { sources.lifecycle };
// Do something on unmounting, e.g. report that to a console
// via some hypothetical `logger` driver
return {
logger: willUnmount$.mapTo(`I'll be unmounted soon!`)
};
}
Side-effect control and __INLINE_CODE_401__ driver
Side-effect control is where Cycle.js really shines (not only implementation, it applies to the "cycle pattern" behind Cycle.js in the first place).
In order to keep the __INLINE_CODE_402__ part of __INLINE_CODE_403__ed component pure, we need to make sure that any potentially effectful function is not run inside our __INLINE_CODE_404__ directly.
This is what __INLINE_CODE_405__ driver is responsible for. It allows to only describe what function you want to call and with what arguments, then delegate that to a __INLINE_CODE_406__ which is supposed to run any impure functions outside of the app itself.
The way it works is very similar to that of __INLINE_CODE_407__ driver from __INLINE_CODE_408__ package where you don't actually run the request inside your __INLINE_CODE_409__ but just describe it and pass to __INLINE_CODE_410__ driver to handle. __INLINE_CODE_411__ driver is just a bit more generic and operates on functions.
__INLINE_CODE_412__ uses __INLINE_CODE_413__ driver internally (to run effects for upstream __INLINE_CODE_414__), but it is also available for you on any connected __INLINE_CODE_415__ as __INLINE_CODE_416__ sink automatically (i.e., without explicit configuration on a root cycle):
function log() {
console.log(`I'm effectful!`);
}
function saveToLocalStorage(message) {
localStorage.setItem('message', message);
}
function main(sources) {
const someCallbackFn$ = sources.props.pluck('someCallbackFunction');
return {
fnCallEffect: xs.merge(
xs.of({ fn: log }),
xs.of({ fn: saveToLocalStorage, args: ['some message to save'] }),
// Call the `props` callback function directly, this is how
// `interactions` sink works behind the scenes:
someCallbackFn$.map(fn => ({ fn }))
)
};
}
Isolation
Isolation is Cycle.js way of making the component's __INLINE_CODE_417__ and __INLINE_CODE_418__ isolated from influence of other components.
Make sure to carefully read more about Cycle.js isolation here. It might take a bit to get used to an idea.
To reiterate: the concept of isolation is important for Cycle.js apps. Its a little bit different for __INLINE_CODE_419__ because most things are scoped to a particular __INLINE_CODE_420__ed component already, like components have "local" __INLINE_CODE_421__, __INLINE_CODE_422__ and __INLINE_CODE_423__ sources and sinks and __INLINE_CODE_424__ components are also self-contained already.
There are though __INLINE_CODE_425__/__INLINE_CODE_426__ that will need the isolation feature, like __INLINE_CODE_427__ driver or __INLINE_CODE_428__ wrapper (a state management solution for Cycle.js apps). So, isolation makes sense for shared __INLINE_CODE_429__/__INLINE_CODE_430__, those you pass to __INLINE_CODE_431__ function (by defining the __INLINE_CODE_432__/__INLINE_CODE_433__ options on root __INLINE_CODE_434__) and that will be available across the entire tree of Cycle programs.
Note: The isolation works by scoping the source streams to particular component and a kind of unscoping of the sink streams before connecting to parent's sinks.
Also note, that each driver/wrapper decides on its own how its sources/sinks should be isolated.
In Cycle.js, you use the __INLINE_CODE_435__ function from __INLINE_CODE_436__ package directly, as you're just invoking the children __INLINE_CODE_437__ functions from your current component's __INLINE_CODE_438__ function:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// Do something with childSinks, combine somehow, etc
return {
...
};
}
__INLINE_CODE_439__ function has this simple signature:
isolate :: (MainFn, scope) => MainFn
It takes the __INLINE_CODE_440__ function and an optional __INLINE_CODE_441__ and returns an isolated __INLINE_CODE_442__ function.
__INLINE_CODE_443__ does also use the __INLINE_CODE_444__ package internally, but you can't call __INLINE_CODE_445__ directly on some __INLINE_CODE_446__ function. You'd never call one component's __INLINE_CODE_447__ from the other component's __INLINE_CODE_448__ directly with __INLINE_CODE_449__.
Naive explicit isolation (conceptually incorrect)
Just to illustrate the issue, there's an attempt to isolate the component's __INLINE_CODE_450__ sources, so that HTTP requests initiated from that components are scoped to it (and are unavailable to siblings).
Since we know that __INLINE_CODE_451__ returns a __INLINE_CODE_452__ function and __INLINE_CODE_453__ takes the __INLINE_CODE_454__ function as an argument, lets try to feed it an isolated version of it:
function main(sources) {
...
}
function MyComponent() {
...
}
const isolatedMain = isolate(main, { HTTP: 'myComponent' });
const ConnectedComponent = cycleConnect(isolatedMain)(MyComponent);
While this will won't raise any errors (because this is valid code after all), this approach brings a subtle issue.
This will isolate things for this particular components but not their children components, so in this case, children (connected childred __INLINE_CODE_455__ components) will have no __INLINE_CODE_456__ isolation scope for __INLINE_CODE_457__ channel.
This might seems like a no-issue, because hey, you can define different scopes for the children components right? Well, while this is technically possible, its conceptually incorrect and it will be completely broken in some cases, e.g., with __INLINE_CODE_458__ where the deeper you go - the "more filtered" source streams you get.
Important! The above note about it being "conceptually incorrect" refers to the way __INLINE_CODE_459__ maintains the tree of Cycle.js programs internally. It tries to make the entire app structure look the same way as you'd manually write a Cycle.js app. So, when you __INLINE_CODE_460__ at some level, then if inside your __INLINE_CODE_461__ you also call some __INLINE_CODE_462__ happens, then __INLINE_CODE_463__ gets isolated sources no matter what.
__INLINE_CODE_464__ option for __INLINE_CODE_465__
The first way to correctly isolate __INLINE_CODE_466__ed component is to use the __INLINE_CODE_467__ option for __INLINE_CODE_468__ itself, so the above can be rewritten as:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main, {
isolate: { HTTP: 'myComponent' }
})(MyComponent);
This will work perfectly, with all children __INLINE_CODE_469__ed components having __INLINE_CODE_470__ namespace applied to __INLINE_CODE_471__ sources/sinks in their __INLINE_CODE_472__ functions.
The __INLINE_CODE_473__ option accepts a __INLINE_CODE_474__ or the __INLINE_CODE_475__ and in that case its passed directly to __INLINE_CODE_476__ package's __INLINE_CODE_477__ as a __INLINE_CODE_478__ argument. So, check out the __INLINE_CODE_479__ function API reference.
__INLINE_CODE_480__ also makes it accept a function that takes component __INLINE_CODE_481__ as an argument and should return the scope of __INLINE_CODE_482__ or __INLINE_CODE_483__ type that is then also passed to __INLINE_CODE_484__ package's __INLINE_CODE_485__. This allows for some dynamic isolation scopes to be set based on the React component's initial props.
Important: Despite the __INLINE_CODE_486__ is provided as an option to __INLINE_CODE_487__ when defining the connected component, the actual isolation will only happen when component instance is created. This is why its possible to dynamically resolve the __INLINE_CODE_488__ based on the component __INLINE_CODE_489__ in __INLINE_CODE_490__.
__INLINE_CODE_491__ prop
Despite the __INLINE_CODE_492__ option will work nicely for some cases, there's a pattern in how it is utilized in pure Cycle.js programs. Lets take a look at pure Cycle.js example again:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// ...
return {
...
};
}
The important part is that in Cycle.js app its not a child who decides how to isolate itself but __INLINE_CODE_493__ is provided from the parent instead.
Most of the time, we want to maintain the same approach with __INLINE_CODE_494__. The advantage is that component in this case is totally agnostic from the provided scope, it doesn't even care whether its isolated or not at all - all it knows is that it expects certain __INLINE_CODE_495__ and returns certain __INLINE_CODE_496__. This makes the component reusable.
This is especially important with __INLINE_CODE_497__, where you want to control where your child components "attach" to the state.
In other words, the parent decides how to compose children components, not the other way around, because otherwise the child would need to have an intimate knowledge of something that really belongs to parent and thus not being reusable and breaking encapsulation.
For this reason, every __INLINE_CODE_498__ed React component accepts an __INLINE_CODE_499__ prop, so you can use that just like any other prop:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
// Isolate everything
<ConnectedComponent isolate="myComponent" />
// Isolate HTTP only
<ConnectedComponent isolate={{ HTTP: 'myComponent' }} />
<ConnectedComponent isolate={{ HTTP: 'myComponent_2' }} />
In contrast with __INLINE_CODE_500__ option, the __INLINE_CODE_501__ prop can only accept __INLINE_CODE_502__ or __INLINE_CODE_503__ that is directly passed to __INLINE_CODE_504__ function from __INLINE_CODE_505__, thus maintaining full parity with pure Cycle.js approach. Indeed, having a function that accepts initial __INLINE_CODE_506__ wouldn't make much sense because you're anyway providing props that could be different for different component instances when rendering that component.
__INLINE_CODE_507__ helper component
Since isolation in __INLINE_CODE_508__ applies to both "current" __INLINE_CODE_509__ed component and all its __INLINE_CODE_510__ed children, we might sometimes need to define some connected component to only be a common parent for some children, which must provide isolation but wouldn't contain any logic.
This is what a helper __INLINE_CODE_511__ component for. It accepts the __INLINE_CODE_512__ prop (that is directly passed as a __INLINE_CODE_513__ argument to __INLINE_CODE_514__ function of __INLINE_CODE_515__):
<Isolator scope={{ HTTP: 'someNamespace' }}>
<ConnectedComponent someProps="1" />
<ConnectedComponent someProps="2" />
<ConnectedComponent someProps="3" />
</Isolator>
This assists the isolation composition at view level.
Extra: State management with __INLINE_CODE_516__
__INLINE_CODE_517__ is the idiomatic Cycle.js fractal state management solution. It is tailored for Cycle.js apps but since __INLINE_CODE_518__ is using the real Cycle.js, it stays fully compatible here as well.
Using __INLINE_CODE_519__ with __INLINE_CODE_520__ is optional, since you might be using different lib (e.g., __INLINE_CODE_521__) for that.
To start, just read the onionify docs and install the package:
$ npm install --save cycle-onionify
and pass that with __INLINE_CODE_522__ array to a __INLINE_CODE_523__ connected component (e.g., Root Cycle):
// RootCycle.js
import { run } from '@cycle/run';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
wrappers
})();
export default RootCycle;
Now every __INLINE_CODE_524__ed component that is a child of the above-mentioned __INLINE_CODE_525__ in React component tree got the __INLINE_CODE_526__ source and sink.
Important: Notice that you're all set - its purely a Cycle.js land. __INLINE_CODE_527__ doesn't provide any additional functionality for __INLINE_CODE_528__. Instead, it provides few helpers for isolation and working with collections on the React side.
All the exportables related to __INLINE_CODE_529__ reside in the extra package: __INLINE_CODE_530__. It is installed along with the main lib but is not __INLINE_CODE_531__d/__INLINE_CODE_532__ed by default, so won't be bundled (by Webpack, Rollup, etc) unless you're using it.
This extra sub-package contains few things:
- __INLINE_CODE_533__ helper component
- __INLINE_CODE_534__ helper component
- __INLINE_CODE_535__ helper factory function
Read ahead for details on each.
In __INLINE_CODE_536__, the state/reducers layer composition is done via isolation, so the first thing here is __INLINE_CODE_537__:
__INLINE_CODE_538__ helper component
This is pretty much like __INLINE_CODE_539__ but is here to specifically isolate __INLINE_CODE_540__ sources/sinks only.
So, the:
import { StateIsolator } from 'react-cycle-connect/lib/extra/onionify';
// ...
<StateIsolator lens="someStateProperty">
<NestedComponent />
</StateIsolator>
is somewhat a convenient shortcut to:
import { Isolator } from 'react-cycle-connect';
// ...
<Isolator scope={{ onion: 'someStateProperty', '*': null }}>
<NestedComponent />
</Isolator>
Notice how __INLINE_CODE_541__'s prop is explicitly named __INLINE_CODE_542__. It just implies that oftentimes you'll be using "lens" to isolate the state. Consider a string just a shortcut to a more elaborate __INLINE_CODE_543__ object.
In the example above, the __INLINE_CODE_544__'s __INLINE_CODE_545__ function __INLINE_CODE_546__ will represent only that "someStateProperty" piece of state. Pretty much how you'd __INLINE_CODE_547__ the __INLINE_CODE_548__ channel in an idiomatic Cycle.js app.
There's also a __INLINE_CODE_549__ property, so that you can change the name of the source/sink inside your __INLINE_CODE_550__ to something else. Thats to be on par with __INLINE_CODE_551__ features, where you can also change that name.
Handling collection state manually
Managing collections of components in pure Cycle.js with __INLINE_CODE_552__ is a kind of a recognized challenge. This is why __INLINE_CODE_553__ ended up providing __INLINE_CODE_554__ helper function - see their docs for more detail.
While __INLINE_CODE_555__ also has a __INLINE_CODE_556__ helper component that is very similar to __INLINE_CODE_557__ __INLINE_CODE_558__ function, lets first try to pretend we don't have it and imagine how'd we handle the state without it.
Note: Nothing prevents you from skipping right to a description of how __INLINE_CODE_559__ works, but it might be useful to understand the rationale behind it, so reading this section is recommended.
Handling collections is different with React than in Cycle.js, e.g., you can always just render React components in a loop or __INLINE_CODE_560__ an array of data to an array of components and React will render that.
When you want to render some __INLINE_CODE_561__ data from your state, its easy without any helpers:
:
{
list: [
{ id: 1, name: 'Obi-Wan Kenobi', kills: 100 },
{ id: 2, name: 'Luke Skywalker', kills: 25 },
{ id: 3, name: 'Darth Vader', kills: 9000 }
]
}
*/
// ListItem.jsx
function ListItem({ itemData }) {
return (
<li>
{itemData.name} (ID: {itemData.id}, kills: {itemData.kills})
</li>
);
}
// List.jsx
function main(sources) {
// Just pass as `props` directly
return {
props: sources.onion.state$
};
}
function List({ list }) {
return (
<ul>
{list.map(item => (
<ListItem key={item.id} itemData={item} />
))}
</ul>
);
}
const ConnectedList = cycleConnect(main)(List);
This keeps your ListItem component just a "dumb" view, there's no even need of connecting it to the Cycle tree. So far so good.
Then you want to add more item-level capabilities, like editing an item (incrementing hero kills in our case, for simplicity) or deleting an item. You can still manage the state in the List component, act upon some callbacks for particular item and only stream reducers from inside List component's main:
/*
Assume you have this value on your `List` component `stateFork
- updated to more recent version of cycle, including @cycle/state
- updated to React 16.9 api, including new lifecycles and context api
- merged onionify separate lib so import is __INLINE_CODE_0__
- added tests
- published at @iilab/react-cycle-connect
react-cycle-connect
An experimental library that allows using Cycle.js as a reactive dataflow framework for React.
Written in TypeScript.
This little thing allows to define your app's view layer using regular React components, but implement the actual logic behind it using pure functional reactive approach, taking advantage of Cycle.js' Model-View-Intent pattern and its exceptional side effect control.
This isn't a custom implementation of Cycle pattern for __INLINE_CODE_1__ but a link to real Cycle.js instead, so any Cycle.js drivers and wrappers can be applied (e.g., __INLINE_CODE_2__ for state management). Also, for this reason, some knowledge about how __INLINE_CODE_3__ works is assumed.
Note: This is an experimental lib and currently works with __INLINE_CODE_4__ and __INLINE_CODE_5__ only. If all goes good, the support for __INLINE_CODE_6__/__INLINE_CODE_7__ (instead of __INLINE_CODE_8__) and for __INLINE_CODE_9__/__INLINE_CODE_10__ (instead of __INLINE_CODE_11__) is anticipated. Nothing prevents from having __INLINE_CODE_12__ covered too.
UPDATE (MAR 2018): Some internals overhaul is pending and it's currently waiting for Cycle.js to settle on hybrid push-pull reactivity and have internals rewritten to use Callbags instead of __INLINE_CODE_13__. So, __INLINE_CODE_14__ is going to be using Callbags by default too.
Any feedback is highly appreciated!
Table of Contents
- react-cycle-connect
- Table of Contents
- Shup up and show me the code
- Installation
- Motivation
- How it works (architecture)
- Guide
- API reference
- Alternatives / Competitors
- FAQ
- Roadmap
Shup up and show me the code
Cycle.js's counter example ported over:
import React from 'react';
import ReactDOM from 'react-dom';
import xs from 'xstream';
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
// Your Cycle.js `main` function
function main(sources) {
const action$ = xs.merge(
sources.interactions.decrement.map(ev => -1),
sources.interactions.increment.map(ev => +1)
);
const count$ = action$.fold((acc, x) => acc + x, 0);
return {
props: count$.map(count => ({ count }))
};
}
// Your React component
function App({ interactions, count }) {
return (
<div>
<button onClick={interactions.decrement}>Decrement</button>
<button onClick={interactions.increment}>Increment</button>
<p>Counter: {count}</p>
</div>
);
}
// Some connection magic
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
// Render normally as it's a regular React app
ReactDOM.render(
<ConnectedApp />,
document.getElementById('root')
);
Also - there's official TodoMVC example with __INLINE_CODE_33__ for state management.
Installation
Using npm:
$ npm install --save react-cycle-connect
Using yarn:
$ yarn add react-cycle-connect
Motivation
The motivation to create this lib was based off the need to retain the React-style composability of the view layer using regular React components, reuse existing React components, make it (the lib) easily/incrementally adoptable in an ongoing React project, but also take advantage of the Cycle.js' MVI pattern and side effect control.
At the same time, while the most valuable thing from __INLINE_CODE_34__ is the pattern itself, there are numerous available libs/plugins for Cycle.js to take advantage of, letting us take the best of both worlds.
This way, you can still use anything from React stack (e.g., __INLINE_CODE_35__ to compose your routed app screens), but describe the interactions as a dataflow, with pure functions and reactive streams. Most of React components are going to be "dumb" (and also stateless functional aka SFC) since all the backing logic and cross-component communication would be handled by a tree of __INLINE_CODE_36__ programs.
Why not just use Cycle.js alone?
That is what I'd recommend considering in the first place. Its fast, its cool, the __INLINE_CODE_37__ (virtual DOM rendering engine behind Cycle's DOM driver) is goddamn fast and reliable. If you don't intend to use __INLINE_CODE_38__ components in any way, don't even get bothered. The __INLINE_CODE_39__ is more about "introducing Cycle.js to __INLINE_CODE_40__", not the other way around.
Is it a "Cycle.js in React" or "React in Cycle.js"?
More of a "Cycle.js in React". The app is bootstrapped by rendering a React component to the DOM, view is composed the React way. The root Cycle.js program is being __INLINE_CODE_41__ by rendering a root __INLINE_CODE_42__ed component. Though, it doesn't prescribe how you write your Cycle.js programs, and aside from the DOM generation part, its just Cycle.js all the way.
How it works (architecture)
Every __INLINE_CODE_43__ed component got a real Cycle.js program "attached" to it. The boundary between the two is __INLINE_CODE_44__. From your Cycle.js program standpoint, __INLINE_CODE_45__ is a view. Cycle.js program can update __INLINE_CODE_46__ that React component will then render.
A bunch of nested React components in your app represent a "tree of React components". __INLINE_CODE_47__ maintains a "tree of Cycle.js programs" that map to __INLINE_CODE_48__ed components and takes advantage of React's context feature to provide links between the nodes. Each connected component provides scoped links to the children nodes .
New "Cycle nodes" are attached lazily, upon associated React component initialization (this is where React controls how Cycle programs are run). The above-mentioned "link" is just an object on React __INLINE_CODE_49__ that brings couple properties:
- __INLINE_CODE_50__ for the child connected component's __INLINE_CODE_51__ function; and
- __INLINE_CODE_52__ to subscribe to that component's __INLINE_CODE_53__ function __INLINE_CODE_54__.
This is where something unusual for the Cycle.js happens - we subscribe to and unsubscribe from connected component's __INLINE_CODE_55__ function __INLINE_CODE_56__ based on React component's lifetime hooks. In Cycle.js, you normally just run the app at startup and let it run as an ongoing process. But this is where __INLINE_CODE_57__ performs the sync between the two worlds.
Overall, aside from using React __INLINE_CODE_58__ (which is just an implementation detail) and some intense wrapping on the __INLINE_CODE_59__ side to make laziness possible, __INLINE_CODE_60__ tries to represent the entire tree of Cycle.js programs as close as possible to what you'd end up with by writing idiomatic Cycle.js with explicit calling of nested component's __INLINE_CODE_61__ functions from the parent component __INLINE_CODE_62__ function. The result of children components is always __INLINE_CODE_63__d.
Aside from passing the __INLINE_CODE_64__ to children components, __INLINE_CODE_65__ also explicitly applies some internal wrappers to integrate with React component: __INLINE_CODE_66__, __INLINE_CODE_67__ and __INLINE_CODE_68__, that are scoped to a connected component's Cycle.js program only. It also explicitly runs the internal __INLINE_CODE_69__ driver for the __INLINE_CODE_70__ nodes by default - this is necessary for the __INLINE_CODE_71__ sink to work (because its supposed to run callbacks passed via __INLINE_CODE_72__ and that might be an effectful function).
Note that the entire internal "tree" of Cycle.js programs is maintained pure, including internal component-scoped wrappers, just as you would do with regular Cycle.js program.
Differences from idiomatic Cycle.js usage
Note: there's a difference in how'd you write the __INLINE_CODE_73__/__INLINE_CODE_74__ app vs pure __INLINE_CODE_75__ app. There's no misuse of Cycle.js per se, we just don't use it for rendering with __INLINE_CODE_76__ - and that applies certain implications to the approach.
__INLINE_CODE_77__ and __INLINE_CODE_78__, while both allow to create user interfaces, imply different approaches to the app architecture. The most important part here is that they are conceptually different when it comes to generating the VDOM.
In order to combine the two approaches nicely, there are certain deviations from how'd you write the __INLINE_CODE_79__-only app to allow for more optimal React DX.
The main difference from a pure __INLINE_CODE_80__ way is that its not a Cycle program that technically generates VDOM - instead, there's React component to represent a piece of UI. __INLINE_CODE_81__ approach to view/__INLINE_CODE_82__ event handling is rather purist - view only defines the markup and nothing else (i.e., user interaction/event handlers are defined inside Cycle program itself and not as some view component __INLINE_CODE_83__/etc attribute). That is not a crazy random opinion but instead a very well thought and weighted out decision by Cycle.js team. __INLINE_CODE_84__ takes a different approach, which isn't that strict and is more in line with React, with callbacks amongst __INLINE_CODE_85__ and all that stuff. It enables the events/component callbacks handling via interactions.
The view composition is obviously completely different. In __INLINE_CODE_86__ you run each component's __INLINE_CODE_87__ function that (possibly) generates a VDOM node and then explicitly combine the pieces of markup into a single VDOM stream that is then given to a DOM driver. With __INLINE_CODE_88__ you compose the view layer with React components normally. You can just connect those to Cycle.js program to "outsource" the actual logic to it.
Guide
This assumes the preliminary knowledge of both __INLINE_CODE_89__ and __INLINE_CODE_90__ (and __INLINE_CODE_91__). It doesn't provide much of the insight on how these work, but is instead focused on how __INLINE_CODE_92__ combines the two.
Basics
The result of __INLINE_CODE_93__ call is a HOC that optionally takes a React component (similar to that __INLINE_CODE_94__ function of __INLINE_CODE_95__) and returns a component that is augmented with backing Cycle.js program. This Cycle.js program is internally wrapped into a "Cycle Node" that, when initialized, is connected to a tree of Cycle.js programs. "Cycle Nodes" of every __INLINE_CODE_96__ed components are linked together via React __INLINE_CODE_97__.
Note, though, that there are no direct references from one component's Cycle program to another component's Cycle program, i.e., you're not invoking one __INLINE_CODE_98__ function from the other (this is perfectly possible though, but since we're not using __INLINE_CODE_99__ here, it would only handle non-DOM stuff). Instead, __INLINE_CODE_100__ handles the linking behind the scenes, so the "sibling" Cycle programs are fed with the same __INLINE_CODE_101__ and subscribe their __INLINE_CODE_102__ to the same __INLINE_CODE_103__.
There's essential concept of isolation in Cycle.js, so every __INLINE_CODE_104__ed component accepts an __INLINE_CODE_105__ prop, that is directly passed to __INLINE_CODE_106__ function from __INLINE_CODE_107__ package. Read more about Cycle.js isolation here.
Root Cycle
Every __INLINE_CODE_108__ed component provides a __INLINE_CODE_109__ object to its children through React __INLINE_CODE_110__ and also expects that __INLINE_CODE_111__ from some parent component (not necessarily immediate parent).
That link object has the following shape:
{
// Sources to provide to our Cycle.js program's `main` function as an argument
sources: { ... },
// sinkProxies to subscribe to our Cycle program's `main` function sinks
sinkProxies: { ... }
}
You shouldn't be working with that directly but its important that something needs to provide the top-level __INLINE_CODE_112__ and __INLINE_CODE_113__ in the first place. This is what __INLINE_CODE_114__ option of cycle connect for.
Also, the __INLINE_CODE_115__ed components' programs (__INLINE_CODE_116__ functions) are lazily attached to already running upper-level Cycle.js program, so the __INLINE_CODE_117__ program has to be __INLINE_CODE_118__.
So, root __INLINE_CODE_119__, aside from __INLINE_CODE_120__ option, also needs __INLINE_CODE_121__ defined, so that it knows how to run your top-level Cycle.js program. This would normally be just a __INLINE_CODE_122__ function from __INLINE_CODE_123__ package (or __INLINE_CODE_124__, or __INLINE_CODE_125__, depending on the reactive streams library in use).
You could just __INLINE_CODE_126__ your top-level __INLINE_CODE_127__ component like we did in the first example:
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
...
function main(sources) {
...
}
function App() {
return (
// Some JSX
...
);
}
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
Though this will work, its advisable to create a specialized __INLINE_CODE_128__ component just for this purpose:
// RootCycle.js
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
function main(sources) {
...
}
export const RootCycle = cycleConnect(main, {
root: true,
runFn: run
})(); // No component to wrap? Its okay, read ahead
export default RootCycle;
The top-level Cycle sources are actually __INLINE_CODE_129__ and __INLINE_CODE_130__, so root Cycle is also in charge of providing these __INLINE_CODE_131__ and __INLINE_CODE_132__ to any Cycle.js program down the tree, as __INLINE_CODE_133__.
__INLINE_CODE_134__ option is the object with keys defining properties of the __INLINE_CODE_135__ object, identical to how you'd do that in a regular Cycle.js program.
__INLINE_CODE_136__ option accepts an array of wrappers (order might matter there, so its a user-land option).
Also, the __INLINE_CODE_137__ function is optional (because you might need a __INLINE_CODE_138__ to be a __INLINE_CODE_139__ or for __INLINE_CODE_140__ only), so you can skip it, unless you have some logic to implement there.
Note that the __INLINE_CODE_141__ can be passed to any __INLINE_CODE_142__, even non-root, but __INLINE_CODE_143__ option is only valid for __INLINE_CODE_144__ Cycle and is ignored otherwise.
// RootCycle.js
import { run } from '@cycle/run';
import { makeHTTPDriver } from '@cycle/http';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const drivers = { HTTP: makeHTTPDriver() };
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
export default RootCycle;
__INLINE_CODE_145__ component is basically in charge of bootstrapping the "Cycle" part of your app here.
Then just wrap your __INLINE_CODE_146__ with it and e.g., render with __INLINE_CODE_147__:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import RootCycle from './RootCycle';
ReactDOM.render(
(
<RootCycle>
<App />
</RootCycle>
),
document.getElementById('root')
);
__INLINE_CODE_148__ option
You might have noticed in axamples above that we're not providing any component to a __INLINE_CODE_149__ HOC, leaving empty braces instead:
const ConnectedComponent = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
Well, that is not a mistake - the wrapped component is optional. When a "component to wrap" is provided to __INLINE_CODE_150__, then if we render the connected component, the __INLINE_CODE_151__ will also render __INLINE_CODE_152__ internally.
Thats all good and works (and what you'll be using most of the time anyway), but sometimes you just want to keep your React Element tree a bit cleaner when the wrapped component would be a kind of noop anyway (just forwarding __INLINE_CODE_153__ and nothing else), or component is some technical/lower-level like __INLINE_CODE_154__, or have some special rendering behavior.
So, we want to avoid seeing this in React Developer Tools:
<cycleConnect(SomeComponent)>
<SomeComponent>
...
</SomeComponent>
</cycleConnect(SomeComponent)>
You can use __INLINE_CODE_155__ option for that. It accepts the function with this signature:
(props) => ReactNode | null
If no component is provided for wrapping, nor __INLINE_CODE_156__ option is defined, the __INLINE_CODE_157__ option defaults to __INLINE_CODE_158__ function, so its either forwarding __INLINE_CODE_159__ if any or renders nothing.
If both __INLINE_CODE_160__ option is provided and component-to-wrap is passed, then __INLINE_CODE_161__ takes the precedence.
Note: Why not use the __INLINE_CODE_162__ option in place of wrapped component all the time? Well, its technically possible, espeically if your React component is a stateless functional component anyway.
But regular HOC wrapping is just common technique in React world and is nicely composable when you have other HOCs than __INLINE_CODE_163__.
And is cleaner too - you might be prototyping your app as a set of __INLINE_CODE_164__ components first, without even involving __INLINE_CODE_165__, then "connect" them all at once, or introduce __INLINE_CODE_166__ incrementally. Whatever :)
__INLINE_CODE_167__ option
Your component's __INLINE_CODE_168__ is by default automatically assigned to the following (depending on whether the wrapped component is provided, or __INLINE_CODE_169__ option, or neither:
- __INLINE_CODE_170__ when WrappedComponent is passed
- __INLINE_CODE_171__ when __INLINE_CODE_172__ prop is defined (and is truthy)
- __INLINE_CODE_173__ otherwise
You can override that like with any regular React component:
const ConnectedComponent = cycleConnect({
...
)(Component);
ConnectedComponent.displayName = 'ConnectedComponent';
But there's also a __INLINE_CODE_174__ option to set the generated (connected) component __INLINE_CODE_175__, which looks a bit cleaner:
const ConnectedComponent = cycleConnect({
...
displayName: 'ConnectedComponent'
)(Component);
Inside the Cycle
__INLINE_CODE_176__ really assumes that you know how to use Cycle.js. Its really a very simple yet very elegant pattern, so make sure to familiarize yourself with it.
Quick recap on what Cycle.js __INLINE_CODE_177__ function is. Its basically a pure function with the signature:
main :: (Sources) => Sinks
It accepts __INLINE_CODE_178__ as an argument and returns __INLINE_CODE_179__ and describes a dataflow using composition of reactive streams and their transformations with pure functions.
Please note that the __INLINE_CODE_180__ function is only run once per __INLINE_CODE_181__ component lifecycle. It doesn't run again on rerendering or something. This is because the actual call to __INLINE_CODE_182__ is to just build up a dataflow, not to get some rendering data.
The data flows through reactive streams, from __INLINE_CODE_183__ to __INLINE_CODE_184__ of the __INLINE_CODE_185__, until its __INLINE_CODE_186__ are unsubscribed from parent Cycle program's __INLINE_CODE_187__ (this happens when React component is unmounted).
Drivers
The __INLINE_CODE_188__ is supposed to run side effects for a Cycle.js program. Its outside the program itself, supposed to be impure and connects your Cycle app with the "outer world". This is an essential concept and is described in a great detail in Cycle.js docs on drivers.
There are no differences from idiomatic Cycle.js in how __INLINE_CODE_189__ handles __INLINE_CODE_190__ - it just hands it over to __INLINE_CODE_191__ directly. There is only one __INLINE_CODE_192__ driver (an __INLINE_CODE_193__ driver) that is always added internally by the __INLINE_CODE_194__ed component - it allows to invoke functions outside the __INLINE_CODE_195__ that is to be maintained pure.
Wrappers
The __INLINE_CODE_196__ is a rather simple thing - its just a function that takes __INLINE_CODE_197__ function that wraps it with the other __INLINE_CODE_198__ function that might in turn add some logic in between:
function someWrapper(originalMain) {
return wrappedMain(sources) {
// Can do something here, like transforming `sources` somehow
// ...
// Invoke the original `main` internally to obtain its sinks
const sinks = originalMain(sources);
// Can do something to `originalMain`s sinks here
// ...
return {
...sinks,
someOtherSink: xs.of({ ... }) // Can add a new sink
};
}
}
It is important that the wrapping __INLINE_CODE_199__ is maintained pure, so that your entire program is pure. If you need some effectful logic to run in a wrapping __INLINE_CODE_200__, you should consider using driver for that. Wrappers, just like any regular __INLINE_CODE_201__ function, can internally use __INLINE_CODE_202__ that are created from __INLINE_CODE_203__.
Wrappers can also add arbitrary __INLINE_CODE_204__ and could expect certain __INLINE_CODE_205__ to be returned from the Cycle program, depending on particular wrapper implementation.
Wrappers are usually created to encapsulate some non-business-logic stuff, you'd be normally describe anything related to the app itself in the "original" __INLINE_CODE_206__ function.
Most of __INLINE_CODE_207__ (aside from __INLINE_CODE_208__ HOC itself) is implemented with wrappers, that are created for each __INLINE_CODE_209__ed component automatically:
- __INLINE_CODE_210__ (to mess around with React component props)
- __INLINE_CODE_211__ (a gateway to React component lifecycle streams)
- __INLINE_CODE_212__ (to route the view events back from your wrapped React component aka view, and pass the events upstream, to parent components)
Sources
This is a Cycle.js program's input. __INLINE_CODE_213__ function shouldn't care whether something on the __INLINE_CODE_214__ is a __INLINE_CODE_215__ or some custom thing provided by one of the wrappers.
For __INLINE_CODE_216__ connected component, the __INLINE_CODE_217__ are __INLINE_CODE_218__ directly plus whatever __INLINE_CODE_219__ add to them.
Sinks
This is a Cycle.js program's output.
__INLINE_CODE_220__ invokes your __INLINE_CODE_221__ed component's __INLINE_CODE_222__ function, takes its __INLINE_CODE_223__ and subscribes the __INLINE_CODE_224__ passed from the parent component to these __INLINE_CODE_225__. Its how __INLINE_CODE_226__ is "attaching" the new component to a tree of Cycle programs.
If there's no parent component (thus, no __INLINE_CODE_227__) and your component is a "Root Cycle", then the sinks are handled to __INLINE_CODE_228__ to "run" as with a regular Cycle.js app.
React __INLINE_CODE_229__
The __INLINE_CODE_230__ed __INLINE_CODE_231__ component is essentially a view for the associated Cycle.js __INLINE_CODE_232__ function. And __INLINE_CODE_233__ encourages the view to be a "dumb" stateless function that takes __INLINE_CODE_234__ as an argument ad returns something of __INLINE_CODE_235__ type (i.e., __INLINE_CODE_236__s, __INLINE_CODE_237__, etc, whatever __INLINE_CODE_238__ component renders).
Note: The wrapped __INLINE_CODE_239__ shouldn't necessarily be a stateless functional component (SFC), you could have any __INLINE_CODE_240__ component wrapped, but its advisable to use functions for the most part as the whole point of __INLINE_CODE_241__ is to use __INLINE_CODE_242__ for anything that is not a view.
So, the __INLINE_CODE_243__ is the only communication channel from our Cycle.js part (the connected component's __INLINE_CODE_244__ function) to connected __INLINE_CODE_245__ component itself. We therefore need to pass the props from our Cycle program to the view (a __INLINE_CODE_246__ component) somehow.
This is where __INLINE_CODE_247__ comes into play and provides __INLINE_CODE_248__ source and sink for a __INLINE_CODE_249__ function. Its local to a component, so no other components will share the same __INLINE_CODE_250__ source or sink.
__INLINE_CODE_251__ sink
The __INLINE_CODE_252__ sink on __INLINE_CODE_253__ed is there to pass the props to the view:
function main(sources) {
return {
props: xs.of({ greetable: 'World' })
};
}
// Use the passed `greetable` prop
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
Remember, that the value you map to __INLINE_CODE_254__ sink should be an object of new props and that updated props will be merged with existing component __INLINE_CODE_255__ like:
const updatedProps = Object.assign({}, prevProps, newProps);
This means that you can easily overwrite any props you want and leave the others intact.
Also, note above that we're passing the __INLINE_CODE_256__ through an __INLINE_CODE_257__ stream and that might be synchronous (like in our example with a stream of static data that is sent immediately) but could also be asynchronous. In that case the value of some prop might be not yet available on the first render:
function main(sources) {
return {
props: xs.of({ greetable: 'World' }).compose(delay(3000))
};
}
// Trying to use the passed `greetable` prop
// But whooops, no `greetable` yet :(
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
So either provide a default value for that async prop's __INLINE_CODE_258__ or perform additional checks in the view, when rendering.
Note: When messing with the __INLINE_CODE_259__ in your __INLINE_CODE_260__, remember that any props that are passed from outside as an input to a __INLINE_CODE_261__ component, are also there (including callback functions, if there are any) and are also merged upon updates from outside (on __INLINE_CODE_262__).
Lets summarize: the __INLINE_CODE_263__ from the __INLINE_CODE_264__ sink are passed from the __INLINE_CODE_265__ function to the view (__INLINE_CODE_266__ component).
__INLINE_CODE_267__ source
Not only you'll need to provide the props to the view for rendering, but oftentimes you'd need your __INLINE_CODE_268__ to take part in transformations/calculations in the __INLINE_CODE_269__ function itself. For that reason, __INLINE_CODE_270__ provides the __INLINE_CODE_271__ source as well.
It allows you to have an __INLINE_CODE_272__ stream of your component's input __INLINE_CODE_273__. Note that the __INLINE_CODE_274__ is of type __INLINE_CODE_275__ and to take the actual stream of props, use its __INLINE_CODE_276__ attribute:
function main(sources) {
const name$ = sources.props.props$.map(props => props.name);
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
function MyComponent(props) {
return (
<div>
Hello, {props.name}! Your father is Mr. {props.father}.
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
...
// Somewhere else:
<MyConnectedComponent name="Luke Skywalker" father="Darth Vader" />
// renders:
//
// <div>
// Hello, LUKE SKYWALKER! Your father is Mr. Darth Vader.
// </div>
The new value on __INLINE_CODE_277__ is emitted every time __INLINE_CODE_278__ change. This could be overkill for the most cases, as you would only need just few props for your dataflow.
This is what __INLINE_CODE_279__ method for, it accepts the name of the nested prop (will return __INLINE_CODE_280__ if the stream value is not an object) and returns a stream of that prop only that is only got new value once this particular prop changes:
function main(sources) {
const name$ = sources.props.pluck('name');
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
The __INLINE_CODE_281__ed stream also performs shallow comparison and does __INLINE_CODE_282__ behind the scenes, so identical values don't cause a new value emitted on an __INLINE_CODE_283__.
Note that you don't need to always pull all props from your __INLINE_CODE_284__ function's __INLINE_CODE_285__ just for the sake of it to just forward to the __INLINE_CODE_286__ sink directly, you better only pick those you need in a dataflow with __INLINE_CODE_287__. The __INLINE_CODE_288__ passed from outside will be available on the view normally. You can overwrite them from inside your __INLINE_CODE_289__ though.
There's also __INLINE_CODE_290__ method that also accepts a nested prop name. The difference is that the result is not an __INLINE_CODE_291__ stream of prop values but the __INLINE_CODE_292__ instance itself that is scoped to that piece of __INLINE_CODE_293__:
function main(sources) {
const name$ = sources.props.pluck('name'); // Stream of strings
const nameSource = sources.props.select('someNestedObject'); // Scoped ReactPropsSource
const someNestedObjProperty$ = nameSource.pluck('someNestedObjectProperty');
...
}
This is useful when you want to __INLINE_CODE_294__ a stream of some sub-prop from deeper nested objects.
Note: Taking deep property stream this way isn't really nice currently as requires quite a bit of boilerplate - this part of API is already being thought out further and reconsidered.
Interactions
So far so good, but rendering the __INLINE_CODE_295__ isn't the only exciting thing we want to do with __INLINE_CODE_296__, right? We also want to handle events coming from the view(e.g., the DOM).
The way __INLINE_CODE_297__ handles events and nested components' callbacks is the biggest deviation from an idiomatic Cycle.js app using __INLINE_CODE_298__ driver. It refrains from a holistic "no handlers in the view" approach for a few reasons:
- We need something more in line with __INLINE_CODE_299__ as we're using React here
- We're not always handling DOM events but also need to handle some children components data/events passed back. We can't pass streams like in regular Cycle.js app, in __INLINE_CODE_300__ we do it via callbacks that are provided amongst children components' __INLINE_CODE_301__ and we need __INLINE_CODE_302__ed components to be compatible __INLINE_CODE_303__ components at all levels.
This is what "interactions" feature of __INLINE_CODE_304__ is for. It provides the unified interface for parent-child components communication and event handling that is aligned with a regular event/callback handling model on __INLINE_CODE_305__ side but also allows to access those events in form of __INLINE_CODE_306__ streams inside the __INLINE_CODE_307__ function.
Events triggering by the view
Every __INLINE_CODE_308__ component has some additional __INLINE_CODE_309__-specific props injected, so they are available on your view.
These are __INLINE_CODE_310__ which is a function that takes the __INLINE_CODE_311__ (a string) and optional __INLINE_CODE_312__ (of any type) and returns a function that will be a callback for your event or some child component prop function.
So, the signature is:
(interactionType, predefinedValue?) => (value) => void
Its supposed to be called imperatively in your view and is available as __INLINE_CODE_313__:
function MyComponent({ interact }) {
return (
<div>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
For the sake of being cleaner (and for better TypeScript support), __INLINE_CODE_314__ also leverages the ES2015 __INLINE_CODE_315__ feature to provide the object with dynamic properties, each representing an __INLINE_CODE_316__ with __INLINE_CODE_317__ dynamically defined after the function property name. Its available on __INLINE_CODE_318__ so we can rewrite the above snippet as:
function MyComponent({ interactions }) {
return (
<div>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
In the example above, the __INLINE_CODE_319__ function receives the click __INLINE_CODE_320__ as an argument and that is what you'll get in your __INLINE_CODE_321__ (next section).
While cleaner, it doesn't allow for the __INLINE_CODE_322__ to be set, so if you need some particular value to be emitted by the associated stream in your __INLINE_CODE_323__, you can either use __INLINE_CODE_324__ or create an inline function:
// Assuming you have `id` available, e.g.
// when looping/mapping over the collection of items
<button
type="button"
className="delete-btn"
onClick={interact('delete', id)}
>
Click me!
</button>
// OR
<button
type="button"
className="delete-btn"
onClick={() => interactions.delete(id)}
>
Click me!
</button>
View events handling with __INLINE_CODE_325__ source
There's internal __INLINE_CODE_326__ in __INLINE_CODE_327__ and it provides the __INLINE_CODE_328__ source for our __INLINE_CODE_329__ function. The __INLINE_CODE_330__ source is of type __INLINE_CODE_331__, which has the __INLINE_CODE_332__ method, which we can use to obtain interaction streams:
function main(sources) {
const buttonClick$ = sources.interactions.select('buttonClick');
// Do something with a buttonClick$, e.g.
// map it to the click coordinates props
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interact, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
The streams are __INLINE_CODE_333__ed by the same __INLINE_CODE_334__ string you provided to the __INLINE_CODE_335__ function on your view.
Similar to how __INLINE_CODE_336__ object with dynamic function-props is available on your view, __INLINE_CODE_337__ is a ES2015 __INLINE_CODE_338__ and it maintains the dynamic streams right on itself (just don't use __INLINE_CODE_339__ as its reserved method name here here):
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interactions, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
Note: If you try to get the non-existing stream, it will still work, this thing is dynamic, so the stream will be created nevertheless but will just never emit values. Be careful and look out for typos! Using TypeScript is highly recommended to avoid typos here.
Upstream interactions with __INLINE_CODE_340__ sink (calling callbacks on __INLINE_CODE_341__).
Now that we can handle the streams of view events and values from the children components, we would also need to somehow call the callbacks that are passed among our __INLINE_CODE_342__.
First, nothing prevents from following the common React way and call those directly from the view:
function MyComponent({ onButtonClick }) {
return (
<button type="button" onClick={onButtonClick}>
Click me!
</button>
);
}
// Somewhere else:
doSomething = (event) => { console.log('clicked!'); };
...
<MyComponent onButtonClick={doSomething} />
This is fine if you don't need that value's stream in your dataflow. This approach bypasses your Cycle.js program's __INLINE_CODE_343__ completely and doesn't make it impure by otherwise possibly passing an effectful function as __INLINE_CODE_344__. There's even no need to __INLINE_CODE_345__ your component in the above example.
Keep in mind that if __INLINE_CODE_346__ is optional there, you will also need to check whether that is defined before providing that as a callback (use __INLINE_CODE_347__ or TypeScript to mark it as either required or optional).
Oftentimes you will need to call some callback that lives on the __INLINE_CODE_348__ though, and this is what __INLINE_CODE_349__ sink provided by __INLINE_CODE_350__ does:
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
const coords$ = buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
);
// We both update the `props` (to render it in this component)
// and pass upstream
return {
props: coords$,
interactions: xs.of({
onCoordsChange: coords$
})
};
}
function MyComponent({ interactions }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
handleCoordsChange = (coords) => {
console.log('New coordinates received: ', coords);
};
...
<ConnectedComponent onCoordsChange={handleCoordsChange} />
// The new coords will be rendered by the component
// and the console will show the "New coordinates received"
// with new coords too.
Once the appropriate stream emits a value, that callback is called. It will also check if the callback was passed in the first place to skip the call attempt if no callback with a given name is passed among the __INLINE_CODE_351__.
Important: Note that __INLINE_CODE_352__ sink expects a stream of object whose property names correspond to callback names on __INLINE_CODE_353__ and property values are themselves streams too - whatever values those streams emit, will be passed as an argument to the callback.
So far so good, but we know that our __INLINE_CODE_354__ is a wrapper and not a driver, so what if one passed the effectful function as a callback, e.g., __INLINE_CODE_355__ passed as __INLINE_CODE_356__ above does a __INLINE_CODE_357__ internally - that is a side effect, right?
Well, this is why the function is not invoked directly, or inside the wrapper (since the wrapper is the part of the pure land of our __INLINE_CODE_358__s), its using the __INLINE_CODE_359__ driver internally. More on this below in a section about Side-effect control, just keep in mind that __INLINE_CODE_360__ and __INLINE_CODE_361__ should be pure and __INLINE_CODE_362__ follows that requirement.
React component lifecycle streams
You're probably not going to need __INLINE_CODE_363__ component lifecycle hooks often inside your __INLINE_CODE_364__, but still - they are provided via special __INLINE_CODE_365__. It provides the __INLINE_CODE_366__ source that is available on each __INLINE_CODE_367__ed __INLINE_CODE_368__ sources.
The lifecycle events are provided as __INLINE_CODE_369__ streams and their names mostly mirror the React lifecycle methods, but shortened to remove some redundant __INLINE_CODE_370__ prefixes:
React lifecycle method
__INLINE_CODE_371__ source property
Stream value
__INLINE_CODE_372__
__INLINE_CODE_373__
__INLINE_CODE_374__
__INLINE_CODE_375__
__INLINE_CODE_376__
__INLINE_CODE_377__
__INLINE_CODE_378__
__INLINE_CODE_379__
__INLINE_CODE_380__
__INLINE_CODE_381__
__INLINE_CODE_382__
__INLINE_CODE_383__
__INLINE_CODE_384__
__INLINE_CODE_385__
__INLINE_CODE_386__
__INLINE_CODE_387__
__INLINE_CODE_388__
__INLINE_CODE_389__
__INLINE_CODE_390__
__INLINE_CODE_391__
Error data
The "error data" on the __INLINE_CODE_392__ is an object of this shape:
{
error: Error;
errorInfo: ErrorInfo;
}
These are basically React __INLINE_CODE_393__ arguments squashed into a single object.
Also, note that __INLINE_CODE_394__ hook is not present there, since that is not really an event and affects how your component is rendered. __INLINE_CODE_395__ would be providing its own custom internal __INLINE_CODE_396__ instead for optimisation purposes and you could implement it on your wrapped component anyway.
Note: As well, no lifecycle events are passing anything related to React component __INLINE_CODE_397__, only taking __INLINE_CODE_398__ into account instead. This is because the __INLINE_CODE_399__ is meant to be managed differently when Cycle.js is in use. You can still use that for wrapped component but its out of play on the component generated by __INLINE_CODE_400__.
A quick example:
function main(sources) {
const willUnmount$ = { sources.lifecycle };
// Do something on unmounting, e.g. report that to a console
// via some hypothetical `logger` driver
return {
logger: willUnmount$.mapTo(`I'll be unmounted soon!`)
};
}
Side-effect control and __INLINE_CODE_401__ driver
Side-effect control is where Cycle.js really shines (not only implementation, it applies to the "cycle pattern" behind Cycle.js in the first place).
In order to keep the __INLINE_CODE_402__ part of __INLINE_CODE_403__ed component pure, we need to make sure that any potentially effectful function is not run inside our __INLINE_CODE_404__ directly.
This is what __INLINE_CODE_405__ driver is responsible for. It allows to only describe what function you want to call and with what arguments, then delegate that to a __INLINE_CODE_406__ which is supposed to run any impure functions outside of the app itself.
The way it works is very similar to that of __INLINE_CODE_407__ driver from __INLINE_CODE_408__ package where you don't actually run the request inside your __INLINE_CODE_409__ but just describe it and pass to __INLINE_CODE_410__ driver to handle. __INLINE_CODE_411__ driver is just a bit more generic and operates on functions.
__INLINE_CODE_412__ uses __INLINE_CODE_413__ driver internally (to run effects for upstream __INLINE_CODE_414__), but it is also available for you on any connected __INLINE_CODE_415__ as __INLINE_CODE_416__ sink automatically (i.e., without explicit configuration on a root cycle):
function log() {
console.log(`I'm effectful!`);
}
function saveToLocalStorage(message) {
localStorage.setItem('message', message);
}
function main(sources) {
const someCallbackFn$ = sources.props.pluck('someCallbackFunction');
return {
fnCallEffect: xs.merge(
xs.of({ fn: log }),
xs.of({ fn: saveToLocalStorage, args: ['some message to save'] }),
// Call the `props` callback function directly, this is how
// `interactions` sink works behind the scenes:
someCallbackFn$.map(fn => ({ fn }))
)
};
}
Isolation
Isolation is Cycle.js way of making the component's __INLINE_CODE_417__ and __INLINE_CODE_418__ isolated from influence of other components.
Make sure to carefully read more about Cycle.js isolation here. It might take a bit to get used to an idea.
To reiterate: the concept of isolation is important for Cycle.js apps. Its a little bit different for __INLINE_CODE_419__ because most things are scoped to a particular __INLINE_CODE_420__ed component already, like components have "local" __INLINE_CODE_421__, __INLINE_CODE_422__ and __INLINE_CODE_423__ sources and sinks and __INLINE_CODE_424__ components are also self-contained already.
There are though __INLINE_CODE_425__/__INLINE_CODE_426__ that will need the isolation feature, like __INLINE_CODE_427__ driver or __INLINE_CODE_428__ wrapper (a state management solution for Cycle.js apps). So, isolation makes sense for shared __INLINE_CODE_429__/__INLINE_CODE_430__, those you pass to __INLINE_CODE_431__ function (by defining the __INLINE_CODE_432__/__INLINE_CODE_433__ options on root __INLINE_CODE_434__) and that will be available across the entire tree of Cycle programs.
Note: The isolation works by scoping the source streams to particular component and a kind of unscoping of the sink streams before connecting to parent's sinks.
Also note, that each driver/wrapper decides on its own how its sources/sinks should be isolated.
In Cycle.js, you use the __INLINE_CODE_435__ function from __INLINE_CODE_436__ package directly, as you're just invoking the children __INLINE_CODE_437__ functions from your current component's __INLINE_CODE_438__ function:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// Do something with childSinks, combine somehow, etc
return {
...
};
}
__INLINE_CODE_439__ function has this simple signature:
isolate :: (MainFn, scope) => MainFn
It takes the __INLINE_CODE_440__ function and an optional __INLINE_CODE_441__ and returns an isolated __INLINE_CODE_442__ function.
__INLINE_CODE_443__ does also use the __INLINE_CODE_444__ package internally, but you can't call __INLINE_CODE_445__ directly on some __INLINE_CODE_446__ function. You'd never call one component's __INLINE_CODE_447__ from the other component's __INLINE_CODE_448__ directly with __INLINE_CODE_449__.
Naive explicit isolation (conceptually incorrect)
Just to illustrate the issue, there's an attempt to isolate the component's __INLINE_CODE_450__ sources, so that HTTP requests initiated from that components are scoped to it (and are unavailable to siblings).
Since we know that __INLINE_CODE_451__ returns a __INLINE_CODE_452__ function and __INLINE_CODE_453__ takes the __INLINE_CODE_454__ function as an argument, lets try to feed it an isolated version of it:
function main(sources) {
...
}
function MyComponent() {
...
}
const isolatedMain = isolate(main, { HTTP: 'myComponent' });
const ConnectedComponent = cycleConnect(isolatedMain)(MyComponent);
While this will won't raise any errors (because this is valid code after all), this approach brings a subtle issue.
This will isolate things for this particular components but not their children components, so in this case, children (connected childred __INLINE_CODE_455__ components) will have no __INLINE_CODE_456__ isolation scope for __INLINE_CODE_457__ channel.
This might seems like a no-issue, because hey, you can define different scopes for the children components right? Well, while this is technically possible, its conceptually incorrect and it will be completely broken in some cases, e.g., with __INLINE_CODE_458__ where the deeper you go - the "more filtered" source streams you get.
Important! The above note about it being "conceptually incorrect" refers to the way __INLINE_CODE_459__ maintains the tree of Cycle.js programs internally. It tries to make the entire app structure look the same way as you'd manually write a Cycle.js app. So, when you __INLINE_CODE_460__ at some level, then if inside your __INLINE_CODE_461__ you also call some __INLINE_CODE_462__ happens, then __INLINE_CODE_463__ gets isolated sources no matter what.
__INLINE_CODE_464__ option for __INLINE_CODE_465__
The first way to correctly isolate __INLINE_CODE_466__ed component is to use the __INLINE_CODE_467__ option for __INLINE_CODE_468__ itself, so the above can be rewritten as:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main, {
isolate: { HTTP: 'myComponent' }
})(MyComponent);
This will work perfectly, with all children __INLINE_CODE_469__ed components having __INLINE_CODE_470__ namespace applied to __INLINE_CODE_471__ sources/sinks in their __INLINE_CODE_472__ functions.
The __INLINE_CODE_473__ option accepts a __INLINE_CODE_474__ or the __INLINE_CODE_475__ and in that case its passed directly to __INLINE_CODE_476__ package's __INLINE_CODE_477__ as a __INLINE_CODE_478__ argument. So, check out the __INLINE_CODE_479__ function API reference.
__INLINE_CODE_480__ also makes it accept a function that takes component __INLINE_CODE_481__ as an argument and should return the scope of __INLINE_CODE_482__ or __INLINE_CODE_483__ type that is then also passed to __INLINE_CODE_484__ package's __INLINE_CODE_485__. This allows for some dynamic isolation scopes to be set based on the React component's initial props.
Important: Despite the __INLINE_CODE_486__ is provided as an option to __INLINE_CODE_487__ when defining the connected component, the actual isolation will only happen when component instance is created. This is why its possible to dynamically resolve the __INLINE_CODE_488__ based on the component __INLINE_CODE_489__ in __INLINE_CODE_490__.
__INLINE_CODE_491__ prop
Despite the __INLINE_CODE_492__ option will work nicely for some cases, there's a pattern in how it is utilized in pure Cycle.js programs. Lets take a look at pure Cycle.js example again:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// ...
return {
...
};
}
The important part is that in Cycle.js app its not a child who decides how to isolate itself but __INLINE_CODE_493__ is provided from the parent instead.
Most of the time, we want to maintain the same approach with __INLINE_CODE_494__. The advantage is that component in this case is totally agnostic from the provided scope, it doesn't even care whether its isolated or not at all - all it knows is that it expects certain __INLINE_CODE_495__ and returns certain __INLINE_CODE_496__. This makes the component reusable.
This is especially important with __INLINE_CODE_497__, where you want to control where your child components "attach" to the state.
In other words, the parent decides how to compose children components, not the other way around, because otherwise the child would need to have an intimate knowledge of something that really belongs to parent and thus not being reusable and breaking encapsulation.
For this reason, every __INLINE_CODE_498__ed React component accepts an __INLINE_CODE_499__ prop, so you can use that just like any other prop:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
// Isolate everything
<ConnectedComponent isolate="myComponent" />
// Isolate HTTP only
<ConnectedComponent isolate={{ HTTP: 'myComponent' }} />
<ConnectedComponent isolate={{ HTTP: 'myComponent_2' }} />
In contrast with __INLINE_CODE_500__ option, the __INLINE_CODE_501__ prop can only accept __INLINE_CODE_502__ or __INLINE_CODE_503__ that is directly passed to __INLINE_CODE_504__ function from __INLINE_CODE_505__, thus maintaining full parity with pure Cycle.js approach. Indeed, having a function that accepts initial __INLINE_CODE_506__ wouldn't make much sense because you're anyway providing props that could be different for different component instances when rendering that component.
__INLINE_CODE_507__ helper component
Since isolation in __INLINE_CODE_508__ applies to both "current" __INLINE_CODE_509__ed component and all its __INLINE_CODE_510__ed children, we might sometimes need to define some connected component to only be a common parent for some children, which must provide isolation but wouldn't contain any logic.
This is what a helper __INLINE_CODE_511__ component for. It accepts the __INLINE_CODE_512__ prop (that is directly passed as a __INLINE_CODE_513__ argument to __INLINE_CODE_514__ function of __INLINE_CODE_515__):
<Isolator scope={{ HTTP: 'someNamespace' }}>
<ConnectedComponent someProps="1" />
<ConnectedComponent someProps="2" />
<ConnectedComponent someProps="3" />
</Isolator>
This assists the isolation composition at view level.
Extra: State management with __INLINE_CODE_516__
__INLINE_CODE_517__ is the idiomatic Cycle.js fractal state management solution. It is tailored for Cycle.js apps but since __INLINE_CODE_518__ is using the real Cycle.js, it stays fully compatible here as well.
Using __INLINE_CODE_519__ with __INLINE_CODE_520__ is optional, since you might be using different lib (e.g., __INLINE_CODE_521__) for that.
To start, just read the onionify docs and install the package:
$ npm install --save cycle-onionify
and pass that with __INLINE_CODE_522__ array to a __INLINE_CODE_523__ connected component (e.g., Root Cycle):
// RootCycle.js
import { run } from '@cycle/run';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
wrappers
})();
export default RootCycle;
Now every __INLINE_CODE_524__ed component that is a child of the above-mentioned __INLINE_CODE_525__ in React component tree got the __INLINE_CODE_526__ source and sink.
Important: Notice that you're all set - its purely a Cycle.js land. __INLINE_CODE_527__ doesn't provide any additional functionality for __INLINE_CODE_528__. Instead, it provides few helpers for isolation and working with collections on the React side.
All the exportables related to __INLINE_CODE_529__ reside in the extra package: __INLINE_CODE_530__. It is installed along with the main lib but is not __INLINE_CODE_531__d/__INLINE_CODE_532__ed by default, so won't be bundled (by Webpack, Rollup, etc) unless you're using it.
This extra sub-package contains few things:
- __INLINE_CODE_533__ helper component
- __INLINE_CODE_534__ helper component
- __INLINE_CODE_535__ helper factory function
Read ahead for details on each.
In __INLINE_CODE_536__, the state/reducers layer composition is done via isolation, so the first thing here is __INLINE_CODE_537__:
__INLINE_CODE_538__ helper component
This is pretty much like __INLINE_CODE_539__ but is here to specifically isolate __INLINE_CODE_540__ sources/sinks only.
So, the:
import { StateIsolator } from 'react-cycle-connect/lib/extra/onionify';
// ...
<StateIsolator lens="someStateProperty">
<NestedComponent />
</StateIsolator>
is somewhat a convenient shortcut to:
import { Isolator } from 'react-cycle-connect';
// ...
<Isolator scope={{ onion: 'someStateProperty', '*': null }}>
<NestedComponent />
</Isolator>
Notice how __INLINE_CODE_541__'s prop is explicitly named __INLINE_CODE_542__. It just implies that oftentimes you'll be using "lens" to isolate the state. Consider a string just a shortcut to a more elaborate __INLINE_CODE_543__ object.
In the example above, the __INLINE_CODE_544__'s __INLINE_CODE_545__ function __INLINE_CODE_546__ will represent only that "someStateProperty" piece of state. Pretty much how you'd __INLINE_CODE_547__ the __INLINE_CODE_548__ channel in an idiomatic Cycle.js app.
There's also a __INLINE_CODE_549__ property, so that you can change the name of the source/sink inside your __INLINE_CODE_550__ to something else. Thats to be on par with __INLINE_CODE_551__ features, where you can also change that name.
Handling collection state manually
Managing collections of components in pure Cycle.js with __INLINE_CODE_552__ is a kind of a recognized challenge. This is why __INLINE_CODE_553__ ended up providing __INLINE_CODE_554__ helper function - see their docs for more detail.
While __INLINE_CODE_555__ also has a __INLINE_CODE_556__ helper component that is very similar to __INLINE_CODE_557__ __INLINE_CODE_558__ function, lets first try to pretend we don't have it and imagine how'd we handle the state without it.
Note: Nothing prevents you from skipping right to a description of how __INLINE_CODE_559__ works, but it might be useful to understand the rationale behind it, so reading this section is recommended.
Handling collections is different with React than in Cycle.js, e.g., you can always just render React components in a loop or __INLINE_CODE_560__ an array of data to an array of components and React will render that.
When you want to render some __INLINE_CODE_561__ data from your state, its easy without any helpers:
/*
Assume you have this value on your `List` component `stateFork
- updated to more recent version of cycle, including @cycle/state
- updated to React 16.9 api, including new lifecycles and context api
- merged onionify separate lib so import is __INLINE_CODE_0__
- added tests
- published at @iilab/react-cycle-connect
react-cycle-connect
An experimental library that allows using Cycle.js as a reactive dataflow framework for React.
Written in TypeScript.
This little thing allows to define your app's view layer using regular React components, but implement the actual logic behind it using pure functional reactive approach, taking advantage of Cycle.js' Model-View-Intent pattern and its exceptional side effect control.
This isn't a custom implementation of Cycle pattern for __INLINE_CODE_1__ but a link to real Cycle.js instead, so any Cycle.js drivers and wrappers can be applied (e.g., __INLINE_CODE_2__ for state management). Also, for this reason, some knowledge about how __INLINE_CODE_3__ works is assumed.
Note: This is an experimental lib and currently works with __INLINE_CODE_4__ and __INLINE_CODE_5__ only. If all goes good, the support for __INLINE_CODE_6__/__INLINE_CODE_7__ (instead of __INLINE_CODE_8__) and for __INLINE_CODE_9__/__INLINE_CODE_10__ (instead of __INLINE_CODE_11__) is anticipated. Nothing prevents from having __INLINE_CODE_12__ covered too.
UPDATE (MAR 2018): Some internals overhaul is pending and it's currently waiting for Cycle.js to settle on hybrid push-pull reactivity and have internals rewritten to use Callbags instead of __INLINE_CODE_13__. So, __INLINE_CODE_14__ is going to be using Callbags by default too.
Any feedback is highly appreciated!
Table of Contents
- react-cycle-connect
- Table of Contents
- Shup up and show me the code
- Installation
- Motivation
- How it works (architecture)
- Guide
- API reference
- Alternatives / Competitors
- FAQ
- Roadmap
Shup up and show me the code
Cycle.js's counter example ported over:
import React from 'react';
import ReactDOM from 'react-dom';
import xs from 'xstream';
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
// Your Cycle.js `main` function
function main(sources) {
const action$ = xs.merge(
sources.interactions.decrement.map(ev => -1),
sources.interactions.increment.map(ev => +1)
);
const count$ = action$.fold((acc, x) => acc + x, 0);
return {
props: count$.map(count => ({ count }))
};
}
// Your React component
function App({ interactions, count }) {
return (
<div>
<button onClick={interactions.decrement}>Decrement</button>
<button onClick={interactions.increment}>Increment</button>
<p>Counter: {count}</p>
</div>
);
}
// Some connection magic
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
// Render normally as it's a regular React app
ReactDOM.render(
<ConnectedApp />,
document.getElementById('root')
);
Also - there's official TodoMVC example with __INLINE_CODE_33__ for state management.
Installation
Using npm:
$ npm install --save react-cycle-connect
Using yarn:
$ yarn add react-cycle-connect
Motivation
The motivation to create this lib was based off the need to retain the React-style composability of the view layer using regular React components, reuse existing React components, make it (the lib) easily/incrementally adoptable in an ongoing React project, but also take advantage of the Cycle.js' MVI pattern and side effect control.
At the same time, while the most valuable thing from __INLINE_CODE_34__ is the pattern itself, there are numerous available libs/plugins for Cycle.js to take advantage of, letting us take the best of both worlds.
This way, you can still use anything from React stack (e.g., __INLINE_CODE_35__ to compose your routed app screens), but describe the interactions as a dataflow, with pure functions and reactive streams. Most of React components are going to be "dumb" (and also stateless functional aka SFC) since all the backing logic and cross-component communication would be handled by a tree of __INLINE_CODE_36__ programs.
Why not just use Cycle.js alone?
That is what I'd recommend considering in the first place. Its fast, its cool, the __INLINE_CODE_37__ (virtual DOM rendering engine behind Cycle's DOM driver) is goddamn fast and reliable. If you don't intend to use __INLINE_CODE_38__ components in any way, don't even get bothered. The __INLINE_CODE_39__ is more about "introducing Cycle.js to __INLINE_CODE_40__", not the other way around.
Is it a "Cycle.js in React" or "React in Cycle.js"?
More of a "Cycle.js in React". The app is bootstrapped by rendering a React component to the DOM, view is composed the React way. The root Cycle.js program is being __INLINE_CODE_41__ by rendering a root __INLINE_CODE_42__ed component. Though, it doesn't prescribe how you write your Cycle.js programs, and aside from the DOM generation part, its just Cycle.js all the way.
How it works (architecture)
Every __INLINE_CODE_43__ed component got a real Cycle.js program "attached" to it. The boundary between the two is __INLINE_CODE_44__. From your Cycle.js program standpoint, __INLINE_CODE_45__ is a view. Cycle.js program can update __INLINE_CODE_46__ that React component will then render.
A bunch of nested React components in your app represent a "tree of React components". __INLINE_CODE_47__ maintains a "tree of Cycle.js programs" that map to __INLINE_CODE_48__ed components and takes advantage of React's context feature to provide links between the nodes. Each connected component provides scoped links to the children nodes .
New "Cycle nodes" are attached lazily, upon associated React component initialization (this is where React controls how Cycle programs are run). The above-mentioned "link" is just an object on React __INLINE_CODE_49__ that brings couple properties:
- __INLINE_CODE_50__ for the child connected component's __INLINE_CODE_51__ function; and
- __INLINE_CODE_52__ to subscribe to that component's __INLINE_CODE_53__ function __INLINE_CODE_54__.
This is where something unusual for the Cycle.js happens - we subscribe to and unsubscribe from connected component's __INLINE_CODE_55__ function __INLINE_CODE_56__ based on React component's lifetime hooks. In Cycle.js, you normally just run the app at startup and let it run as an ongoing process. But this is where __INLINE_CODE_57__ performs the sync between the two worlds.
Overall, aside from using React __INLINE_CODE_58__ (which is just an implementation detail) and some intense wrapping on the __INLINE_CODE_59__ side to make laziness possible, __INLINE_CODE_60__ tries to represent the entire tree of Cycle.js programs as close as possible to what you'd end up with by writing idiomatic Cycle.js with explicit calling of nested component's __INLINE_CODE_61__ functions from the parent component __INLINE_CODE_62__ function. The result of children components is always __INLINE_CODE_63__d.
Aside from passing the __INLINE_CODE_64__ to children components, __INLINE_CODE_65__ also explicitly applies some internal wrappers to integrate with React component: __INLINE_CODE_66__, __INLINE_CODE_67__ and __INLINE_CODE_68__, that are scoped to a connected component's Cycle.js program only. It also explicitly runs the internal __INLINE_CODE_69__ driver for the __INLINE_CODE_70__ nodes by default - this is necessary for the __INLINE_CODE_71__ sink to work (because its supposed to run callbacks passed via __INLINE_CODE_72__ and that might be an effectful function).
Note that the entire internal "tree" of Cycle.js programs is maintained pure, including internal component-scoped wrappers, just as you would do with regular Cycle.js program.
Differences from idiomatic Cycle.js usage
Note: there's a difference in how'd you write the __INLINE_CODE_73__/__INLINE_CODE_74__ app vs pure __INLINE_CODE_75__ app. There's no misuse of Cycle.js per se, we just don't use it for rendering with __INLINE_CODE_76__ - and that applies certain implications to the approach.
__INLINE_CODE_77__ and __INLINE_CODE_78__, while both allow to create user interfaces, imply different approaches to the app architecture. The most important part here is that they are conceptually different when it comes to generating the VDOM.
In order to combine the two approaches nicely, there are certain deviations from how'd you write the __INLINE_CODE_79__-only app to allow for more optimal React DX.
The main difference from a pure __INLINE_CODE_80__ way is that its not a Cycle program that technically generates VDOM - instead, there's React component to represent a piece of UI. __INLINE_CODE_81__ approach to view/__INLINE_CODE_82__ event handling is rather purist - view only defines the markup and nothing else (i.e., user interaction/event handlers are defined inside Cycle program itself and not as some view component __INLINE_CODE_83__/etc attribute). That is not a crazy random opinion but instead a very well thought and weighted out decision by Cycle.js team. __INLINE_CODE_84__ takes a different approach, which isn't that strict and is more in line with React, with callbacks amongst __INLINE_CODE_85__ and all that stuff. It enables the events/component callbacks handling via interactions.
The view composition is obviously completely different. In __INLINE_CODE_86__ you run each component's __INLINE_CODE_87__ function that (possibly) generates a VDOM node and then explicitly combine the pieces of markup into a single VDOM stream that is then given to a DOM driver. With __INLINE_CODE_88__ you compose the view layer with React components normally. You can just connect those to Cycle.js program to "outsource" the actual logic to it.
Guide
This assumes the preliminary knowledge of both __INLINE_CODE_89__ and __INLINE_CODE_90__ (and __INLINE_CODE_91__). It doesn't provide much of the insight on how these work, but is instead focused on how __INLINE_CODE_92__ combines the two.
Basics
The result of __INLINE_CODE_93__ call is a HOC that optionally takes a React component (similar to that __INLINE_CODE_94__ function of __INLINE_CODE_95__) and returns a component that is augmented with backing Cycle.js program. This Cycle.js program is internally wrapped into a "Cycle Node" that, when initialized, is connected to a tree of Cycle.js programs. "Cycle Nodes" of every __INLINE_CODE_96__ed components are linked together via React __INLINE_CODE_97__.
Note, though, that there are no direct references from one component's Cycle program to another component's Cycle program, i.e., you're not invoking one __INLINE_CODE_98__ function from the other (this is perfectly possible though, but since we're not using __INLINE_CODE_99__ here, it would only handle non-DOM stuff). Instead, __INLINE_CODE_100__ handles the linking behind the scenes, so the "sibling" Cycle programs are fed with the same __INLINE_CODE_101__ and subscribe their __INLINE_CODE_102__ to the same __INLINE_CODE_103__.
There's essential concept of isolation in Cycle.js, so every __INLINE_CODE_104__ed component accepts an __INLINE_CODE_105__ prop, that is directly passed to __INLINE_CODE_106__ function from __INLINE_CODE_107__ package. Read more about Cycle.js isolation here.
Root Cycle
Every __INLINE_CODE_108__ed component provides a __INLINE_CODE_109__ object to its children through React __INLINE_CODE_110__ and also expects that __INLINE_CODE_111__ from some parent component (not necessarily immediate parent).
That link object has the following shape:
{
// Sources to provide to our Cycle.js program's `main` function as an argument
sources: { ... },
// sinkProxies to subscribe to our Cycle program's `main` function sinks
sinkProxies: { ... }
}
You shouldn't be working with that directly but its important that something needs to provide the top-level __INLINE_CODE_112__ and __INLINE_CODE_113__ in the first place. This is what __INLINE_CODE_114__ option of cycle connect for.
Also, the __INLINE_CODE_115__ed components' programs (__INLINE_CODE_116__ functions) are lazily attached to already running upper-level Cycle.js program, so the __INLINE_CODE_117__ program has to be __INLINE_CODE_118__.
So, root __INLINE_CODE_119__, aside from __INLINE_CODE_120__ option, also needs __INLINE_CODE_121__ defined, so that it knows how to run your top-level Cycle.js program. This would normally be just a __INLINE_CODE_122__ function from __INLINE_CODE_123__ package (or __INLINE_CODE_124__, or __INLINE_CODE_125__, depending on the reactive streams library in use).
You could just __INLINE_CODE_126__ your top-level __INLINE_CODE_127__ component like we did in the first example:
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
...
function main(sources) {
...
}
function App() {
return (
// Some JSX
...
);
}
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
Though this will work, its advisable to create a specialized __INLINE_CODE_128__ component just for this purpose:
// RootCycle.js
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
function main(sources) {
...
}
export const RootCycle = cycleConnect(main, {
root: true,
runFn: run
})(); // No component to wrap? Its okay, read ahead
export default RootCycle;
The top-level Cycle sources are actually __INLINE_CODE_129__ and __INLINE_CODE_130__, so root Cycle is also in charge of providing these __INLINE_CODE_131__ and __INLINE_CODE_132__ to any Cycle.js program down the tree, as __INLINE_CODE_133__.
__INLINE_CODE_134__ option is the object with keys defining properties of the __INLINE_CODE_135__ object, identical to how you'd do that in a regular Cycle.js program.
__INLINE_CODE_136__ option accepts an array of wrappers (order might matter there, so its a user-land option).
Also, the __INLINE_CODE_137__ function is optional (because you might need a __INLINE_CODE_138__ to be a __INLINE_CODE_139__ or for __INLINE_CODE_140__ only), so you can skip it, unless you have some logic to implement there.
Note that the __INLINE_CODE_141__ can be passed to any __INLINE_CODE_142__, even non-root, but __INLINE_CODE_143__ option is only valid for __INLINE_CODE_144__ Cycle and is ignored otherwise.
// RootCycle.js
import { run } from '@cycle/run';
import { makeHTTPDriver } from '@cycle/http';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const drivers = { HTTP: makeHTTPDriver() };
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
export default RootCycle;
__INLINE_CODE_145__ component is basically in charge of bootstrapping the "Cycle" part of your app here.
Then just wrap your __INLINE_CODE_146__ with it and e.g., render with __INLINE_CODE_147__:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import RootCycle from './RootCycle';
ReactDOM.render(
(
<RootCycle>
<App />
</RootCycle>
),
document.getElementById('root')
);
__INLINE_CODE_148__ option
You might have noticed in axamples above that we're not providing any component to a __INLINE_CODE_149__ HOC, leaving empty braces instead:
const ConnectedComponent = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
Well, that is not a mistake - the wrapped component is optional. When a "component to wrap" is provided to __INLINE_CODE_150__, then if we render the connected component, the __INLINE_CODE_151__ will also render __INLINE_CODE_152__ internally.
Thats all good and works (and what you'll be using most of the time anyway), but sometimes you just want to keep your React Element tree a bit cleaner when the wrapped component would be a kind of noop anyway (just forwarding __INLINE_CODE_153__ and nothing else), or component is some technical/lower-level like __INLINE_CODE_154__, or have some special rendering behavior.
So, we want to avoid seeing this in React Developer Tools:
<cycleConnect(SomeComponent)>
<SomeComponent>
...
</SomeComponent>
</cycleConnect(SomeComponent)>
You can use __INLINE_CODE_155__ option for that. It accepts the function with this signature:
(props) => ReactNode | null
If no component is provided for wrapping, nor __INLINE_CODE_156__ option is defined, the __INLINE_CODE_157__ option defaults to __INLINE_CODE_158__ function, so its either forwarding __INLINE_CODE_159__ if any or renders nothing.
If both __INLINE_CODE_160__ option is provided and component-to-wrap is passed, then __INLINE_CODE_161__ takes the precedence.
Note: Why not use the __INLINE_CODE_162__ option in place of wrapped component all the time? Well, its technically possible, espeically if your React component is a stateless functional component anyway.
But regular HOC wrapping is just common technique in React world and is nicely composable when you have other HOCs than __INLINE_CODE_163__.
And is cleaner too - you might be prototyping your app as a set of __INLINE_CODE_164__ components first, without even involving __INLINE_CODE_165__, then "connect" them all at once, or introduce __INLINE_CODE_166__ incrementally. Whatever :)
__INLINE_CODE_167__ option
Your component's __INLINE_CODE_168__ is by default automatically assigned to the following (depending on whether the wrapped component is provided, or __INLINE_CODE_169__ option, or neither:
- __INLINE_CODE_170__ when WrappedComponent is passed
- __INLINE_CODE_171__ when __INLINE_CODE_172__ prop is defined (and is truthy)
- __INLINE_CODE_173__ otherwise
You can override that like with any regular React component:
const ConnectedComponent = cycleConnect({
...
)(Component);
ConnectedComponent.displayName = 'ConnectedComponent';
But there's also a __INLINE_CODE_174__ option to set the generated (connected) component __INLINE_CODE_175__, which looks a bit cleaner:
const ConnectedComponent = cycleConnect({
...
displayName: 'ConnectedComponent'
)(Component);
Inside the Cycle
__INLINE_CODE_176__ really assumes that you know how to use Cycle.js. Its really a very simple yet very elegant pattern, so make sure to familiarize yourself with it.
Quick recap on what Cycle.js __INLINE_CODE_177__ function is. Its basically a pure function with the signature:
main :: (Sources) => Sinks
It accepts __INLINE_CODE_178__ as an argument and returns __INLINE_CODE_179__ and describes a dataflow using composition of reactive streams and their transformations with pure functions.
Please note that the __INLINE_CODE_180__ function is only run once per __INLINE_CODE_181__ component lifecycle. It doesn't run again on rerendering or something. This is because the actual call to __INLINE_CODE_182__ is to just build up a dataflow, not to get some rendering data.
The data flows through reactive streams, from __INLINE_CODE_183__ to __INLINE_CODE_184__ of the __INLINE_CODE_185__, until its __INLINE_CODE_186__ are unsubscribed from parent Cycle program's __INLINE_CODE_187__ (this happens when React component is unmounted).
Drivers
The __INLINE_CODE_188__ is supposed to run side effects for a Cycle.js program. Its outside the program itself, supposed to be impure and connects your Cycle app with the "outer world". This is an essential concept and is described in a great detail in Cycle.js docs on drivers.
There are no differences from idiomatic Cycle.js in how __INLINE_CODE_189__ handles __INLINE_CODE_190__ - it just hands it over to __INLINE_CODE_191__ directly. There is only one __INLINE_CODE_192__ driver (an __INLINE_CODE_193__ driver) that is always added internally by the __INLINE_CODE_194__ed component - it allows to invoke functions outside the __INLINE_CODE_195__ that is to be maintained pure.
Wrappers
The __INLINE_CODE_196__ is a rather simple thing - its just a function that takes __INLINE_CODE_197__ function that wraps it with the other __INLINE_CODE_198__ function that might in turn add some logic in between:
function someWrapper(originalMain) {
return wrappedMain(sources) {
// Can do something here, like transforming `sources` somehow
// ...
// Invoke the original `main` internally to obtain its sinks
const sinks = originalMain(sources);
// Can do something to `originalMain`s sinks here
// ...
return {
...sinks,
someOtherSink: xs.of({ ... }) // Can add a new sink
};
}
}
It is important that the wrapping __INLINE_CODE_199__ is maintained pure, so that your entire program is pure. If you need some effectful logic to run in a wrapping __INLINE_CODE_200__, you should consider using driver for that. Wrappers, just like any regular __INLINE_CODE_201__ function, can internally use __INLINE_CODE_202__ that are created from __INLINE_CODE_203__.
Wrappers can also add arbitrary __INLINE_CODE_204__ and could expect certain __INLINE_CODE_205__ to be returned from the Cycle program, depending on particular wrapper implementation.
Wrappers are usually created to encapsulate some non-business-logic stuff, you'd be normally describe anything related to the app itself in the "original" __INLINE_CODE_206__ function.
Most of __INLINE_CODE_207__ (aside from __INLINE_CODE_208__ HOC itself) is implemented with wrappers, that are created for each __INLINE_CODE_209__ed component automatically:
- __INLINE_CODE_210__ (to mess around with React component props)
- __INLINE_CODE_211__ (a gateway to React component lifecycle streams)
- __INLINE_CODE_212__ (to route the view events back from your wrapped React component aka view, and pass the events upstream, to parent components)
Sources
This is a Cycle.js program's input. __INLINE_CODE_213__ function shouldn't care whether something on the __INLINE_CODE_214__ is a __INLINE_CODE_215__ or some custom thing provided by one of the wrappers.
For __INLINE_CODE_216__ connected component, the __INLINE_CODE_217__ are __INLINE_CODE_218__ directly plus whatever __INLINE_CODE_219__ add to them.
Sinks
This is a Cycle.js program's output.
__INLINE_CODE_220__ invokes your __INLINE_CODE_221__ed component's __INLINE_CODE_222__ function, takes its __INLINE_CODE_223__ and subscribes the __INLINE_CODE_224__ passed from the parent component to these __INLINE_CODE_225__. Its how __INLINE_CODE_226__ is "attaching" the new component to a tree of Cycle programs.
If there's no parent component (thus, no __INLINE_CODE_227__) and your component is a "Root Cycle", then the sinks are handled to __INLINE_CODE_228__ to "run" as with a regular Cycle.js app.
React __INLINE_CODE_229__
The __INLINE_CODE_230__ed __INLINE_CODE_231__ component is essentially a view for the associated Cycle.js __INLINE_CODE_232__ function. And __INLINE_CODE_233__ encourages the view to be a "dumb" stateless function that takes __INLINE_CODE_234__ as an argument ad returns something of __INLINE_CODE_235__ type (i.e., __INLINE_CODE_236__s, __INLINE_CODE_237__, etc, whatever __INLINE_CODE_238__ component renders).
Note: The wrapped __INLINE_CODE_239__ shouldn't necessarily be a stateless functional component (SFC), you could have any __INLINE_CODE_240__ component wrapped, but its advisable to use functions for the most part as the whole point of __INLINE_CODE_241__ is to use __INLINE_CODE_242__ for anything that is not a view.
So, the __INLINE_CODE_243__ is the only communication channel from our Cycle.js part (the connected component's __INLINE_CODE_244__ function) to connected __INLINE_CODE_245__ component itself. We therefore need to pass the props from our Cycle program to the view (a __INLINE_CODE_246__ component) somehow.
This is where __INLINE_CODE_247__ comes into play and provides __INLINE_CODE_248__ source and sink for a __INLINE_CODE_249__ function. Its local to a component, so no other components will share the same __INLINE_CODE_250__ source or sink.
__INLINE_CODE_251__ sink
The __INLINE_CODE_252__ sink on __INLINE_CODE_253__ed is there to pass the props to the view:
function main(sources) {
return {
props: xs.of({ greetable: 'World' })
};
}
// Use the passed `greetable` prop
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
Remember, that the value you map to __INLINE_CODE_254__ sink should be an object of new props and that updated props will be merged with existing component __INLINE_CODE_255__ like:
const updatedProps = Object.assign({}, prevProps, newProps);
This means that you can easily overwrite any props you want and leave the others intact.
Also, note above that we're passing the __INLINE_CODE_256__ through an __INLINE_CODE_257__ stream and that might be synchronous (like in our example with a stream of static data that is sent immediately) but could also be asynchronous. In that case the value of some prop might be not yet available on the first render:
function main(sources) {
return {
props: xs.of({ greetable: 'World' }).compose(delay(3000))
};
}
// Trying to use the passed `greetable` prop
// But whooops, no `greetable` yet :(
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
So either provide a default value for that async prop's __INLINE_CODE_258__ or perform additional checks in the view, when rendering.
Note: When messing with the __INLINE_CODE_259__ in your __INLINE_CODE_260__, remember that any props that are passed from outside as an input to a __INLINE_CODE_261__ component, are also there (including callback functions, if there are any) and are also merged upon updates from outside (on __INLINE_CODE_262__).
Lets summarize: the __INLINE_CODE_263__ from the __INLINE_CODE_264__ sink are passed from the __INLINE_CODE_265__ function to the view (__INLINE_CODE_266__ component).
__INLINE_CODE_267__ source
Not only you'll need to provide the props to the view for rendering, but oftentimes you'd need your __INLINE_CODE_268__ to take part in transformations/calculations in the __INLINE_CODE_269__ function itself. For that reason, __INLINE_CODE_270__ provides the __INLINE_CODE_271__ source as well.
It allows you to have an __INLINE_CODE_272__ stream of your component's input __INLINE_CODE_273__. Note that the __INLINE_CODE_274__ is of type __INLINE_CODE_275__ and to take the actual stream of props, use its __INLINE_CODE_276__ attribute:
function main(sources) {
const name$ = sources.props.props$.map(props => props.name);
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
function MyComponent(props) {
return (
<div>
Hello, {props.name}! Your father is Mr. {props.father}.
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
...
// Somewhere else:
<MyConnectedComponent name="Luke Skywalker" father="Darth Vader" />
// renders:
//
// <div>
// Hello, LUKE SKYWALKER! Your father is Mr. Darth Vader.
// </div>
The new value on __INLINE_CODE_277__ is emitted every time __INLINE_CODE_278__ change. This could be overkill for the most cases, as you would only need just few props for your dataflow.
This is what __INLINE_CODE_279__ method for, it accepts the name of the nested prop (will return __INLINE_CODE_280__ if the stream value is not an object) and returns a stream of that prop only that is only got new value once this particular prop changes:
function main(sources) {
const name$ = sources.props.pluck('name');
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
The __INLINE_CODE_281__ed stream also performs shallow comparison and does __INLINE_CODE_282__ behind the scenes, so identical values don't cause a new value emitted on an __INLINE_CODE_283__.
Note that you don't need to always pull all props from your __INLINE_CODE_284__ function's __INLINE_CODE_285__ just for the sake of it to just forward to the __INLINE_CODE_286__ sink directly, you better only pick those you need in a dataflow with __INLINE_CODE_287__. The __INLINE_CODE_288__ passed from outside will be available on the view normally. You can overwrite them from inside your __INLINE_CODE_289__ though.
There's also __INLINE_CODE_290__ method that also accepts a nested prop name. The difference is that the result is not an __INLINE_CODE_291__ stream of prop values but the __INLINE_CODE_292__ instance itself that is scoped to that piece of __INLINE_CODE_293__:
function main(sources) {
const name$ = sources.props.pluck('name'); // Stream of strings
const nameSource = sources.props.select('someNestedObject'); // Scoped ReactPropsSource
const someNestedObjProperty$ = nameSource.pluck('someNestedObjectProperty');
...
}
This is useful when you want to __INLINE_CODE_294__ a stream of some sub-prop from deeper nested objects.
Note: Taking deep property stream this way isn't really nice currently as requires quite a bit of boilerplate - this part of API is already being thought out further and reconsidered.
Interactions
So far so good, but rendering the __INLINE_CODE_295__ isn't the only exciting thing we want to do with __INLINE_CODE_296__, right? We also want to handle events coming from the view(e.g., the DOM).
The way __INLINE_CODE_297__ handles events and nested components' callbacks is the biggest deviation from an idiomatic Cycle.js app using __INLINE_CODE_298__ driver. It refrains from a holistic "no handlers in the view" approach for a few reasons:
- We need something more in line with __INLINE_CODE_299__ as we're using React here
- We're not always handling DOM events but also need to handle some children components data/events passed back. We can't pass streams like in regular Cycle.js app, in __INLINE_CODE_300__ we do it via callbacks that are provided amongst children components' __INLINE_CODE_301__ and we need __INLINE_CODE_302__ed components to be compatible __INLINE_CODE_303__ components at all levels.
This is what "interactions" feature of __INLINE_CODE_304__ is for. It provides the unified interface for parent-child components communication and event handling that is aligned with a regular event/callback handling model on __INLINE_CODE_305__ side but also allows to access those events in form of __INLINE_CODE_306__ streams inside the __INLINE_CODE_307__ function.
Events triggering by the view
Every __INLINE_CODE_308__ component has some additional __INLINE_CODE_309__-specific props injected, so they are available on your view.
These are __INLINE_CODE_310__ which is a function that takes the __INLINE_CODE_311__ (a string) and optional __INLINE_CODE_312__ (of any type) and returns a function that will be a callback for your event or some child component prop function.
So, the signature is:
(interactionType, predefinedValue?) => (value) => void
Its supposed to be called imperatively in your view and is available as __INLINE_CODE_313__:
function MyComponent({ interact }) {
return (
<div>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
For the sake of being cleaner (and for better TypeScript support), __INLINE_CODE_314__ also leverages the ES2015 __INLINE_CODE_315__ feature to provide the object with dynamic properties, each representing an __INLINE_CODE_316__ with __INLINE_CODE_317__ dynamically defined after the function property name. Its available on __INLINE_CODE_318__ so we can rewrite the above snippet as:
function MyComponent({ interactions }) {
return (
<div>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
In the example above, the __INLINE_CODE_319__ function receives the click __INLINE_CODE_320__ as an argument and that is what you'll get in your __INLINE_CODE_321__ (next section).
While cleaner, it doesn't allow for the __INLINE_CODE_322__ to be set, so if you need some particular value to be emitted by the associated stream in your __INLINE_CODE_323__, you can either use __INLINE_CODE_324__ or create an inline function:
// Assuming you have `id` available, e.g.
// when looping/mapping over the collection of items
<button
type="button"
className="delete-btn"
onClick={interact('delete', id)}
>
Click me!
</button>
// OR
<button
type="button"
className="delete-btn"
onClick={() => interactions.delete(id)}
>
Click me!
</button>
View events handling with __INLINE_CODE_325__ source
There's internal __INLINE_CODE_326__ in __INLINE_CODE_327__ and it provides the __INLINE_CODE_328__ source for our __INLINE_CODE_329__ function. The __INLINE_CODE_330__ source is of type __INLINE_CODE_331__, which has the __INLINE_CODE_332__ method, which we can use to obtain interaction streams:
function main(sources) {
const buttonClick$ = sources.interactions.select('buttonClick');
// Do something with a buttonClick$, e.g.
// map it to the click coordinates props
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interact, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
The streams are __INLINE_CODE_333__ed by the same __INLINE_CODE_334__ string you provided to the __INLINE_CODE_335__ function on your view.
Similar to how __INLINE_CODE_336__ object with dynamic function-props is available on your view, __INLINE_CODE_337__ is a ES2015 __INLINE_CODE_338__ and it maintains the dynamic streams right on itself (just don't use __INLINE_CODE_339__ as its reserved method name here here):
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interactions, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
Note: If you try to get the non-existing stream, it will still work, this thing is dynamic, so the stream will be created nevertheless but will just never emit values. Be careful and look out for typos! Using TypeScript is highly recommended to avoid typos here.
Upstream interactions with __INLINE_CODE_340__ sink (calling callbacks on __INLINE_CODE_341__).
Now that we can handle the streams of view events and values from the children components, we would also need to somehow call the callbacks that are passed among our __INLINE_CODE_342__.
First, nothing prevents from following the common React way and call those directly from the view:
function MyComponent({ onButtonClick }) {
return (
<button type="button" onClick={onButtonClick}>
Click me!
</button>
);
}
// Somewhere else:
doSomething = (event) => { console.log('clicked!'); };
...
<MyComponent onButtonClick={doSomething} />
This is fine if you don't need that value's stream in your dataflow. This approach bypasses your Cycle.js program's __INLINE_CODE_343__ completely and doesn't make it impure by otherwise possibly passing an effectful function as __INLINE_CODE_344__. There's even no need to __INLINE_CODE_345__ your component in the above example.
Keep in mind that if __INLINE_CODE_346__ is optional there, you will also need to check whether that is defined before providing that as a callback (use __INLINE_CODE_347__ or TypeScript to mark it as either required or optional).
Oftentimes you will need to call some callback that lives on the __INLINE_CODE_348__ though, and this is what __INLINE_CODE_349__ sink provided by __INLINE_CODE_350__ does:
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
const coords$ = buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
);
// We both update the `props` (to render it in this component)
// and pass upstream
return {
props: coords$,
interactions: xs.of({
onCoordsChange: coords$
})
};
}
function MyComponent({ interactions }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
handleCoordsChange = (coords) => {
console.log('New coordinates received: ', coords);
};
...
<ConnectedComponent onCoordsChange={handleCoordsChange} />
// The new coords will be rendered by the component
// and the console will show the "New coordinates received"
// with new coords too.
Once the appropriate stream emits a value, that callback is called. It will also check if the callback was passed in the first place to skip the call attempt if no callback with a given name is passed among the __INLINE_CODE_351__.
Important: Note that __INLINE_CODE_352__ sink expects a stream of object whose property names correspond to callback names on __INLINE_CODE_353__ and property values are themselves streams too - whatever values those streams emit, will be passed as an argument to the callback.
So far so good, but we know that our __INLINE_CODE_354__ is a wrapper and not a driver, so what if one passed the effectful function as a callback, e.g., __INLINE_CODE_355__ passed as __INLINE_CODE_356__ above does a __INLINE_CODE_357__ internally - that is a side effect, right?
Well, this is why the function is not invoked directly, or inside the wrapper (since the wrapper is the part of the pure land of our __INLINE_CODE_358__s), its using the __INLINE_CODE_359__ driver internally. More on this below in a section about Side-effect control, just keep in mind that __INLINE_CODE_360__ and __INLINE_CODE_361__ should be pure and __INLINE_CODE_362__ follows that requirement.
React component lifecycle streams
You're probably not going to need __INLINE_CODE_363__ component lifecycle hooks often inside your __INLINE_CODE_364__, but still - they are provided via special __INLINE_CODE_365__. It provides the __INLINE_CODE_366__ source that is available on each __INLINE_CODE_367__ed __INLINE_CODE_368__ sources.
The lifecycle events are provided as __INLINE_CODE_369__ streams and their names mostly mirror the React lifecycle methods, but shortened to remove some redundant __INLINE_CODE_370__ prefixes:
React lifecycle method
__INLINE_CODE_371__ source property
Stream value
__INLINE_CODE_372__
__INLINE_CODE_373__
__INLINE_CODE_374__
__INLINE_CODE_375__
__INLINE_CODE_376__
__INLINE_CODE_377__
__INLINE_CODE_378__
__INLINE_CODE_379__
__INLINE_CODE_380__
__INLINE_CODE_381__
__INLINE_CODE_382__
__INLINE_CODE_383__
__INLINE_CODE_384__
__INLINE_CODE_385__
__INLINE_CODE_386__
__INLINE_CODE_387__
__INLINE_CODE_388__
__INLINE_CODE_389__
__INLINE_CODE_390__
__INLINE_CODE_391__
Error data
The "error data" on the __INLINE_CODE_392__ is an object of this shape:
{
error: Error;
errorInfo: ErrorInfo;
}
These are basically React __INLINE_CODE_393__ arguments squashed into a single object.
Also, note that __INLINE_CODE_394__ hook is not present there, since that is not really an event and affects how your component is rendered. __INLINE_CODE_395__ would be providing its own custom internal __INLINE_CODE_396__ instead for optimisation purposes and you could implement it on your wrapped component anyway.
Note: As well, no lifecycle events are passing anything related to React component __INLINE_CODE_397__, only taking __INLINE_CODE_398__ into account instead. This is because the __INLINE_CODE_399__ is meant to be managed differently when Cycle.js is in use. You can still use that for wrapped component but its out of play on the component generated by __INLINE_CODE_400__.
A quick example:
function main(sources) {
const willUnmount$ = { sources.lifecycle };
// Do something on unmounting, e.g. report that to a console
// via some hypothetical `logger` driver
return {
logger: willUnmount$.mapTo(`I'll be unmounted soon!`)
};
}
Side-effect control and __INLINE_CODE_401__ driver
Side-effect control is where Cycle.js really shines (not only implementation, it applies to the "cycle pattern" behind Cycle.js in the first place).
In order to keep the __INLINE_CODE_402__ part of __INLINE_CODE_403__ed component pure, we need to make sure that any potentially effectful function is not run inside our __INLINE_CODE_404__ directly.
This is what __INLINE_CODE_405__ driver is responsible for. It allows to only describe what function you want to call and with what arguments, then delegate that to a __INLINE_CODE_406__ which is supposed to run any impure functions outside of the app itself.
The way it works is very similar to that of __INLINE_CODE_407__ driver from __INLINE_CODE_408__ package where you don't actually run the request inside your __INLINE_CODE_409__ but just describe it and pass to __INLINE_CODE_410__ driver to handle. __INLINE_CODE_411__ driver is just a bit more generic and operates on functions.
__INLINE_CODE_412__ uses __INLINE_CODE_413__ driver internally (to run effects for upstream __INLINE_CODE_414__), but it is also available for you on any connected __INLINE_CODE_415__ as __INLINE_CODE_416__ sink automatically (i.e., without explicit configuration on a root cycle):
function log() {
console.log(`I'm effectful!`);
}
function saveToLocalStorage(message) {
localStorage.setItem('message', message);
}
function main(sources) {
const someCallbackFn$ = sources.props.pluck('someCallbackFunction');
return {
fnCallEffect: xs.merge(
xs.of({ fn: log }),
xs.of({ fn: saveToLocalStorage, args: ['some message to save'] }),
// Call the `props` callback function directly, this is how
// `interactions` sink works behind the scenes:
someCallbackFn$.map(fn => ({ fn }))
)
};
}
Isolation
Isolation is Cycle.js way of making the component's __INLINE_CODE_417__ and __INLINE_CODE_418__ isolated from influence of other components.
Make sure to carefully read more about Cycle.js isolation here. It might take a bit to get used to an idea.
To reiterate: the concept of isolation is important for Cycle.js apps. Its a little bit different for __INLINE_CODE_419__ because most things are scoped to a particular __INLINE_CODE_420__ed component already, like components have "local" __INLINE_CODE_421__, __INLINE_CODE_422__ and __INLINE_CODE_423__ sources and sinks and __INLINE_CODE_424__ components are also self-contained already.
There are though __INLINE_CODE_425__/__INLINE_CODE_426__ that will need the isolation feature, like __INLINE_CODE_427__ driver or __INLINE_CODE_428__ wrapper (a state management solution for Cycle.js apps). So, isolation makes sense for shared __INLINE_CODE_429__/__INLINE_CODE_430__, those you pass to __INLINE_CODE_431__ function (by defining the __INLINE_CODE_432__/__INLINE_CODE_433__ options on root __INLINE_CODE_434__) and that will be available across the entire tree of Cycle programs.
Note: The isolation works by scoping the source streams to particular component and a kind of unscoping of the sink streams before connecting to parent's sinks.
Also note, that each driver/wrapper decides on its own how its sources/sinks should be isolated.
In Cycle.js, you use the __INLINE_CODE_435__ function from __INLINE_CODE_436__ package directly, as you're just invoking the children __INLINE_CODE_437__ functions from your current component's __INLINE_CODE_438__ function:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// Do something with childSinks, combine somehow, etc
return {
...
};
}
__INLINE_CODE_439__ function has this simple signature:
isolate :: (MainFn, scope) => MainFn
It takes the __INLINE_CODE_440__ function and an optional __INLINE_CODE_441__ and returns an isolated __INLINE_CODE_442__ function.
__INLINE_CODE_443__ does also use the __INLINE_CODE_444__ package internally, but you can't call __INLINE_CODE_445__ directly on some __INLINE_CODE_446__ function. You'd never call one component's __INLINE_CODE_447__ from the other component's __INLINE_CODE_448__ directly with __INLINE_CODE_449__.
Naive explicit isolation (conceptually incorrect)
Just to illustrate the issue, there's an attempt to isolate the component's __INLINE_CODE_450__ sources, so that HTTP requests initiated from that components are scoped to it (and are unavailable to siblings).
Since we know that __INLINE_CODE_451__ returns a __INLINE_CODE_452__ function and __INLINE_CODE_453__ takes the __INLINE_CODE_454__ function as an argument, lets try to feed it an isolated version of it:
function main(sources) {
...
}
function MyComponent() {
...
}
const isolatedMain = isolate(main, { HTTP: 'myComponent' });
const ConnectedComponent = cycleConnect(isolatedMain)(MyComponent);
While this will won't raise any errors (because this is valid code after all), this approach brings a subtle issue.
This will isolate things for this particular components but not their children components, so in this case, children (connected childred __INLINE_CODE_455__ components) will have no __INLINE_CODE_456__ isolation scope for __INLINE_CODE_457__ channel.
This might seems like a no-issue, because hey, you can define different scopes for the children components right? Well, while this is technically possible, its conceptually incorrect and it will be completely broken in some cases, e.g., with __INLINE_CODE_458__ where the deeper you go - the "more filtered" source streams you get.
Important! The above note about it being "conceptually incorrect" refers to the way __INLINE_CODE_459__ maintains the tree of Cycle.js programs internally. It tries to make the entire app structure look the same way as you'd manually write a Cycle.js app. So, when you __INLINE_CODE_460__ at some level, then if inside your __INLINE_CODE_461__ you also call some __INLINE_CODE_462__ happens, then __INLINE_CODE_463__ gets isolated sources no matter what.
__INLINE_CODE_464__ option for __INLINE_CODE_465__
The first way to correctly isolate __INLINE_CODE_466__ed component is to use the __INLINE_CODE_467__ option for __INLINE_CODE_468__ itself, so the above can be rewritten as:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main, {
isolate: { HTTP: 'myComponent' }
})(MyComponent);
This will work perfectly, with all children __INLINE_CODE_469__ed components having __INLINE_CODE_470__ namespace applied to __INLINE_CODE_471__ sources/sinks in their __INLINE_CODE_472__ functions.
The __INLINE_CODE_473__ option accepts a __INLINE_CODE_474__ or the __INLINE_CODE_475__ and in that case its passed directly to __INLINE_CODE_476__ package's __INLINE_CODE_477__ as a __INLINE_CODE_478__ argument. So, check out the __INLINE_CODE_479__ function API reference.
__INLINE_CODE_480__ also makes it accept a function that takes component __INLINE_CODE_481__ as an argument and should return the scope of __INLINE_CODE_482__ or __INLINE_CODE_483__ type that is then also passed to __INLINE_CODE_484__ package's __INLINE_CODE_485__. This allows for some dynamic isolation scopes to be set based on the React component's initial props.
Important: Despite the __INLINE_CODE_486__ is provided as an option to __INLINE_CODE_487__ when defining the connected component, the actual isolation will only happen when component instance is created. This is why its possible to dynamically resolve the __INLINE_CODE_488__ based on the component __INLINE_CODE_489__ in __INLINE_CODE_490__.
__INLINE_CODE_491__ prop
Despite the __INLINE_CODE_492__ option will work nicely for some cases, there's a pattern in how it is utilized in pure Cycle.js programs. Lets take a look at pure Cycle.js example again:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// ...
return {
...
};
}
The important part is that in Cycle.js app its not a child who decides how to isolate itself but __INLINE_CODE_493__ is provided from the parent instead.
Most of the time, we want to maintain the same approach with __INLINE_CODE_494__. The advantage is that component in this case is totally agnostic from the provided scope, it doesn't even care whether its isolated or not at all - all it knows is that it expects certain __INLINE_CODE_495__ and returns certain __INLINE_CODE_496__. This makes the component reusable.
This is especially important with __INLINE_CODE_497__, where you want to control where your child components "attach" to the state.
In other words, the parent decides how to compose children components, not the other way around, because otherwise the child would need to have an intimate knowledge of something that really belongs to parent and thus not being reusable and breaking encapsulation.
For this reason, every __INLINE_CODE_498__ed React component accepts an __INLINE_CODE_499__ prop, so you can use that just like any other prop:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
// Isolate everything
<ConnectedComponent isolate="myComponent" />
// Isolate HTTP only
<ConnectedComponent isolate={{ HTTP: 'myComponent' }} />
<ConnectedComponent isolate={{ HTTP: 'myComponent_2' }} />
In contrast with __INLINE_CODE_500__ option, the __INLINE_CODE_501__ prop can only accept __INLINE_CODE_502__ or __INLINE_CODE_503__ that is directly passed to __INLINE_CODE_504__ function from __INLINE_CODE_505__, thus maintaining full parity with pure Cycle.js approach. Indeed, having a function that accepts initial __INLINE_CODE_506__ wouldn't make much sense because you're anyway providing props that could be different for different component instances when rendering that component.
__INLINE_CODE_507__ helper component
Since isolation in __INLINE_CODE_508__ applies to both "current" __INLINE_CODE_509__ed component and all its __INLINE_CODE_510__ed children, we might sometimes need to define some connected component to only be a common parent for some children, which must provide isolation but wouldn't contain any logic.
This is what a helper __INLINE_CODE_511__ component for. It accepts the __INLINE_CODE_512__ prop (that is directly passed as a __INLINE_CODE_513__ argument to __INLINE_CODE_514__ function of __INLINE_CODE_515__):
<Isolator scope={{ HTTP: 'someNamespace' }}>
<ConnectedComponent someProps="1" />
<ConnectedComponent someProps="2" />
<ConnectedComponent someProps="3" />
</Isolator>
This assists the isolation composition at view level.
Extra: State management with __INLINE_CODE_516__
__INLINE_CODE_517__ is the idiomatic Cycle.js fractal state management solution. It is tailored for Cycle.js apps but since __INLINE_CODE_518__ is using the real Cycle.js, it stays fully compatible here as well.
Using __INLINE_CODE_519__ with __INLINE_CODE_520__ is optional, since you might be using different lib (e.g., __INLINE_CODE_521__) for that.
To start, just read the onionify docs and install the package:
$ npm install --save cycle-onionify
and pass that with __INLINE_CODE_522__ array to a __INLINE_CODE_523__ connected component (e.g., Root Cycle):
// RootCycle.js
import { run } from '@cycle/run';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
wrappers
})();
export default RootCycle;
Now every __INLINE_CODE_524__ed component that is a child of the above-mentioned __INLINE_CODE_525__ in React component tree got the __INLINE_CODE_526__ source and sink.
Important: Notice that you're all set - its purely a Cycle.js land. __INLINE_CODE_527__ doesn't provide any additional functionality for __INLINE_CODE_528__. Instead, it provides few helpers for isolation and working with collections on the React side.
All the exportables related to __INLINE_CODE_529__ reside in the extra package: __INLINE_CODE_530__. It is installed along with the main lib but is not __INLINE_CODE_531__d/__INLINE_CODE_532__ed by default, so won't be bundled (by Webpack, Rollup, etc) unless you're using it.
This extra sub-package contains few things:
- __INLINE_CODE_533__ helper component
- __INLINE_CODE_534__ helper component
- __INLINE_CODE_535__ helper factory function
Read ahead for details on each.
In __INLINE_CODE_536__, the state/reducers layer composition is done via isolation, so the first thing here is __INLINE_CODE_537__:
__INLINE_CODE_538__ helper component
This is pretty much like __INLINE_CODE_539__ but is here to specifically isolate __INLINE_CODE_540__ sources/sinks only.
So, the:
import { StateIsolator } from 'react-cycle-connect/lib/extra/onionify';
// ...
<StateIsolator lens="someStateProperty">
<NestedComponent />
</StateIsolator>
is somewhat a convenient shortcut to:
import { Isolator } from 'react-cycle-connect';
// ...
<Isolator scope={{ onion: 'someStateProperty', '*': null }}>
<NestedComponent />
</Isolator>
Notice how __INLINE_CODE_541__'s prop is explicitly named __INLINE_CODE_542__. It just implies that oftentimes you'll be using "lens" to isolate the state. Consider a string just a shortcut to a more elaborate __INLINE_CODE_543__ object.
In the example above, the __INLINE_CODE_544__'s __INLINE_CODE_545__ function __INLINE_CODE_546__ will represent only that "someStateProperty" piece of state. Pretty much how you'd __INLINE_CODE_547__ the __INLINE_CODE_548__ channel in an idiomatic Cycle.js app.
There's also a __INLINE_CODE_549__ property, so that you can change the name of the source/sink inside your __INLINE_CODE_550__ to something else. Thats to be on par with __INLINE_CODE_551__ features, where you can also change that name.
Handling collection state manually
Managing collections of components in pure Cycle.js with __INLINE_CODE_552__ is a kind of a recognized challenge. This is why __INLINE_CODE_553__ ended up providing __INLINE_CODE_554__ helper function - see their docs for more detail.
While __INLINE_CODE_555__ also has a __INLINE_CODE_556__ helper component that is very similar to __INLINE_CODE_557__ __INLINE_CODE_558__ function, lets first try to pretend we don't have it and imagine how'd we handle the state without it.
Note: Nothing prevents you from skipping right to a description of how __INLINE_CODE_559__ works, but it might be useful to understand the rationale behind it, so reading this section is recommended.
Handling collections is different with React than in Cycle.js, e.g., you can always just render React components in a loop or __INLINE_CODE_560__ an array of data to an array of components and React will render that.
When you want to render some __INLINE_CODE_561__ data from your state, its easy without any helpers:
:
{
list: [
{ id: 1, name: 'Obi-Wan Kenobi', kills: 100 },
{ id: 2, name: 'Luke Skywalker', kills: 25 },
{ id: 3, name: 'Darth Vader', kills: 9000 }
]
}
*/
// ListItem.jsx
function ListItem({ itemData }) {
return (
<li>
{itemData.name} (ID: {itemData.id}, kills: {itemData.kills})
</li>
);
}
// List.jsx
function main(sources) {
// Just pass as `props` directly
return {
props: sources.onion.state$
};
}
function List({ list }) {
return (
<ul>
{list.map(item => (
<ListItem key={item.id} itemData={item} />
))}
</ul>
);
}
const ConnectedList = cycleConnect(main)(List);
This keeps your __INLINE_CODE_562__ component just a "dumb" view, there's no even need of connecting it to the Cycle tree. So far so good.
Then you want to add more item-level capabilities, like editing an item (incrementing hero __INLINE_CODE_563__ in our case, for simplicity) or deleting an item. You can still manage the state in the __INLINE_CODE_564__ component, act upon some callbacks for particular item and only stream reducers from inside __INLINE_CODE_565__ component's __INLINE_CODE_566__:
:
{
list: [
{ id: 1, name: 'Obi-Wan Kenobi', kills: 100 },
{ id: 2, name: 'Luke Skywalker', kills: 25 },
{ id: 3, name: 'Darth Vader', kills: 9000 }
]
}
*/
// ListItem.jsx
function ListItem({ itemData, onIncrementClick, onDeleteClick }) {
return (
<li>
{itemData.name} (ID: {itemData.id}, kills: {itemData.kills}) -
<button type="button" onClick={onIncrementClick}>
Increment "kills"
</button>
<button type="button" onClick={onDeleteClick}>
Delete
</button>
</li>
);
}
// List.jsx
function intent(interactions) {
return {
itemIncrement$: interactions.itemIncrementClick,
itemDelete$: interactions.itemDeleteClick
};
}
function model(actions) {
const defaultReducer$ = xs.of(function defaultReducer(state) {
if (typeof state !== 'undefined') return state;
return {
list: []
};
});
const incrementItemReducer$ = actions.itemIncrement$.map(
(id) => function incrementItemReducer(state) {
return {
...state,
list: state.list.map(listItem => {
if (listItem.id !== id) return listItem;
return {
...listItem,
kills: listItem.kills + 1
};
})
};
}
);
const deleteItemReducer$ = actions.itemDelete$.map(
(id) => function deleteItemReducer(state) {
return {
...state,
list: state.list.filter(listItem => listItem.id !== id)
};
}
);
return xs.merge(
defaultReducer$,
incrementItemReducer$,
deleteItemReducer$
);
}
function main(sources) {
const actions = intent(sources.interactions);
const reducer$ = model(actions);
return {
onion: reducer$,
props: sources.onion.state$
};
}
function List({ interactions, list }) {
return (
<ul>
{list.map(item => (
<ListItem
key={item.id}
itemData={item}
onIncrementClick={() => interactions.itemIncrementClick(item.id)}
onDeleteClick={() => interactions.itemDeleteClick(item.id)}
/>
))}
</ul>
);
}
const ConnectedList = cycleConnect(main)(List);
There is already some noise in reducers, especially on updates. Also, what if there's another piece of state sibling to list property? List gets overburdened quickly.
You'd also quickly realize that this makes the collection item state inherently bound to List, so while you could be reusing ListItem somewhere else, you couldn't reuse its state management part.
This is unfortunate because certain actions ("edit" and "delete") are related to a single item and can be managed at that item's level. This is totally possible with cycle-onionify because its a fractal state management thing, so we could be taking advantage of that fractal aspect with react-cycle-connect too.
So, we'd better be managing internal item state at the ListItem component level. To do that, let's cycleConnect the ListItem component and make it handle its stuff. We need to carefully let each item's component know which piece of state does belong to it, and we'll use listLens for that:
/*
Assume you have this value on your `List` component `stateFork
- updated to more recent version of cycle, including @cycle/state
- updated to React 16.9 api, including new lifecycles and context api
- merged onionify separate lib so import is __INLINE_CODE_0__
- added tests
- published at @iilab/react-cycle-connect
react-cycle-connect
An experimental library that allows using Cycle.js as a reactive dataflow framework for React.
Written in TypeScript.
This little thing allows to define your app's view layer using regular React components, but implement the actual logic behind it using pure functional reactive approach, taking advantage of Cycle.js' Model-View-Intent pattern and its exceptional side effect control.
This isn't a custom implementation of Cycle pattern for __INLINE_CODE_1__ but a link to real Cycle.js instead, so any Cycle.js drivers and wrappers can be applied (e.g., __INLINE_CODE_2__ for state management). Also, for this reason, some knowledge about how __INLINE_CODE_3__ works is assumed.
Note: This is an experimental lib and currently works with __INLINE_CODE_4__ and __INLINE_CODE_5__ only. If all goes good, the support for __INLINE_CODE_6__/__INLINE_CODE_7__ (instead of __INLINE_CODE_8__) and for __INLINE_CODE_9__/__INLINE_CODE_10__ (instead of __INLINE_CODE_11__) is anticipated. Nothing prevents from having __INLINE_CODE_12__ covered too.
UPDATE (MAR 2018): Some internals overhaul is pending and it's currently waiting for Cycle.js to settle on hybrid push-pull reactivity and have internals rewritten to use Callbags instead of __INLINE_CODE_13__. So, __INLINE_CODE_14__ is going to be using Callbags by default too.
Any feedback is highly appreciated!
Table of Contents
- react-cycle-connect
- Table of Contents
- Shup up and show me the code
- Installation
- Motivation
- How it works (architecture)
- Guide
- API reference
- Alternatives / Competitors
- FAQ
- Roadmap
Shup up and show me the code
Cycle.js's counter example ported over:
import React from 'react';
import ReactDOM from 'react-dom';
import xs from 'xstream';
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
// Your Cycle.js `main` function
function main(sources) {
const action$ = xs.merge(
sources.interactions.decrement.map(ev => -1),
sources.interactions.increment.map(ev => +1)
);
const count$ = action$.fold((acc, x) => acc + x, 0);
return {
props: count$.map(count => ({ count }))
};
}
// Your React component
function App({ interactions, count }) {
return (
<div>
<button onClick={interactions.decrement}>Decrement</button>
<button onClick={interactions.increment}>Increment</button>
<p>Counter: {count}</p>
</div>
);
}
// Some connection magic
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
// Render normally as it's a regular React app
ReactDOM.render(
<ConnectedApp />,
document.getElementById('root')
);
Also - there's official TodoMVC example with __INLINE_CODE_33__ for state management.
Installation
Using npm:
$ npm install --save react-cycle-connect
Using yarn:
$ yarn add react-cycle-connect
Motivation
The motivation to create this lib was based off the need to retain the React-style composability of the view layer using regular React components, reuse existing React components, make it (the lib) easily/incrementally adoptable in an ongoing React project, but also take advantage of the Cycle.js' MVI pattern and side effect control.
At the same time, while the most valuable thing from __INLINE_CODE_34__ is the pattern itself, there are numerous available libs/plugins for Cycle.js to take advantage of, letting us take the best of both worlds.
This way, you can still use anything from React stack (e.g., __INLINE_CODE_35__ to compose your routed app screens), but describe the interactions as a dataflow, with pure functions and reactive streams. Most of React components are going to be "dumb" (and also stateless functional aka SFC) since all the backing logic and cross-component communication would be handled by a tree of __INLINE_CODE_36__ programs.
Why not just use Cycle.js alone?
That is what I'd recommend considering in the first place. Its fast, its cool, the __INLINE_CODE_37__ (virtual DOM rendering engine behind Cycle's DOM driver) is goddamn fast and reliable. If you don't intend to use __INLINE_CODE_38__ components in any way, don't even get bothered. The __INLINE_CODE_39__ is more about "introducing Cycle.js to __INLINE_CODE_40__", not the other way around.
Is it a "Cycle.js in React" or "React in Cycle.js"?
More of a "Cycle.js in React". The app is bootstrapped by rendering a React component to the DOM, view is composed the React way. The root Cycle.js program is being __INLINE_CODE_41__ by rendering a root __INLINE_CODE_42__ed component. Though, it doesn't prescribe how you write your Cycle.js programs, and aside from the DOM generation part, its just Cycle.js all the way.
How it works (architecture)
Every __INLINE_CODE_43__ed component got a real Cycle.js program "attached" to it. The boundary between the two is __INLINE_CODE_44__. From your Cycle.js program standpoint, __INLINE_CODE_45__ is a view. Cycle.js program can update __INLINE_CODE_46__ that React component will then render.
A bunch of nested React components in your app represent a "tree of React components". __INLINE_CODE_47__ maintains a "tree of Cycle.js programs" that map to __INLINE_CODE_48__ed components and takes advantage of React's context feature to provide links between the nodes. Each connected component provides scoped links to the children nodes .
New "Cycle nodes" are attached lazily, upon associated React component initialization (this is where React controls how Cycle programs are run). The above-mentioned "link" is just an object on React __INLINE_CODE_49__ that brings couple properties:
- __INLINE_CODE_50__ for the child connected component's __INLINE_CODE_51__ function; and
- __INLINE_CODE_52__ to subscribe to that component's __INLINE_CODE_53__ function __INLINE_CODE_54__.
This is where something unusual for the Cycle.js happens - we subscribe to and unsubscribe from connected component's __INLINE_CODE_55__ function __INLINE_CODE_56__ based on React component's lifetime hooks. In Cycle.js, you normally just run the app at startup and let it run as an ongoing process. But this is where __INLINE_CODE_57__ performs the sync between the two worlds.
Overall, aside from using React __INLINE_CODE_58__ (which is just an implementation detail) and some intense wrapping on the __INLINE_CODE_59__ side to make laziness possible, __INLINE_CODE_60__ tries to represent the entire tree of Cycle.js programs as close as possible to what you'd end up with by writing idiomatic Cycle.js with explicit calling of nested component's __INLINE_CODE_61__ functions from the parent component __INLINE_CODE_62__ function. The result of children components is always __INLINE_CODE_63__d.
Aside from passing the __INLINE_CODE_64__ to children components, __INLINE_CODE_65__ also explicitly applies some internal wrappers to integrate with React component: __INLINE_CODE_66__, __INLINE_CODE_67__ and __INLINE_CODE_68__, that are scoped to a connected component's Cycle.js program only. It also explicitly runs the internal __INLINE_CODE_69__ driver for the __INLINE_CODE_70__ nodes by default - this is necessary for the __INLINE_CODE_71__ sink to work (because its supposed to run callbacks passed via __INLINE_CODE_72__ and that might be an effectful function).
Note that the entire internal "tree" of Cycle.js programs is maintained pure, including internal component-scoped wrappers, just as you would do with regular Cycle.js program.
Differences from idiomatic Cycle.js usage
Note: there's a difference in how'd you write the __INLINE_CODE_73__/__INLINE_CODE_74__ app vs pure __INLINE_CODE_75__ app. There's no misuse of Cycle.js per se, we just don't use it for rendering with __INLINE_CODE_76__ - and that applies certain implications to the approach.
__INLINE_CODE_77__ and __INLINE_CODE_78__, while both allow to create user interfaces, imply different approaches to the app architecture. The most important part here is that they are conceptually different when it comes to generating the VDOM.
In order to combine the two approaches nicely, there are certain deviations from how'd you write the __INLINE_CODE_79__-only app to allow for more optimal React DX.
The main difference from a pure __INLINE_CODE_80__ way is that its not a Cycle program that technically generates VDOM - instead, there's React component to represent a piece of UI. __INLINE_CODE_81__ approach to view/__INLINE_CODE_82__ event handling is rather purist - view only defines the markup and nothing else (i.e., user interaction/event handlers are defined inside Cycle program itself and not as some view component __INLINE_CODE_83__/etc attribute). That is not a crazy random opinion but instead a very well thought and weighted out decision by Cycle.js team. __INLINE_CODE_84__ takes a different approach, which isn't that strict and is more in line with React, with callbacks amongst __INLINE_CODE_85__ and all that stuff. It enables the events/component callbacks handling via interactions.
The view composition is obviously completely different. In __INLINE_CODE_86__ you run each component's __INLINE_CODE_87__ function that (possibly) generates a VDOM node and then explicitly combine the pieces of markup into a single VDOM stream that is then given to a DOM driver. With __INLINE_CODE_88__ you compose the view layer with React components normally. You can just connect those to Cycle.js program to "outsource" the actual logic to it.
Guide
This assumes the preliminary knowledge of both __INLINE_CODE_89__ and __INLINE_CODE_90__ (and __INLINE_CODE_91__). It doesn't provide much of the insight on how these work, but is instead focused on how __INLINE_CODE_92__ combines the two.
Basics
The result of __INLINE_CODE_93__ call is a HOC that optionally takes a React component (similar to that __INLINE_CODE_94__ function of __INLINE_CODE_95__) and returns a component that is augmented with backing Cycle.js program. This Cycle.js program is internally wrapped into a "Cycle Node" that, when initialized, is connected to a tree of Cycle.js programs. "Cycle Nodes" of every __INLINE_CODE_96__ed components are linked together via React __INLINE_CODE_97__.
Note, though, that there are no direct references from one component's Cycle program to another component's Cycle program, i.e., you're not invoking one __INLINE_CODE_98__ function from the other (this is perfectly possible though, but since we're not using __INLINE_CODE_99__ here, it would only handle non-DOM stuff). Instead, __INLINE_CODE_100__ handles the linking behind the scenes, so the "sibling" Cycle programs are fed with the same __INLINE_CODE_101__ and subscribe their __INLINE_CODE_102__ to the same __INLINE_CODE_103__.
There's essential concept of isolation in Cycle.js, so every __INLINE_CODE_104__ed component accepts an __INLINE_CODE_105__ prop, that is directly passed to __INLINE_CODE_106__ function from __INLINE_CODE_107__ package. Read more about Cycle.js isolation here.
Root Cycle
Every __INLINE_CODE_108__ed component provides a __INLINE_CODE_109__ object to its children through React __INLINE_CODE_110__ and also expects that __INLINE_CODE_111__ from some parent component (not necessarily immediate parent).
That link object has the following shape:
{
// Sources to provide to our Cycle.js program's `main` function as an argument
sources: { ... },
// sinkProxies to subscribe to our Cycle program's `main` function sinks
sinkProxies: { ... }
}
You shouldn't be working with that directly but its important that something needs to provide the top-level __INLINE_CODE_112__ and __INLINE_CODE_113__ in the first place. This is what __INLINE_CODE_114__ option of cycle connect for.
Also, the __INLINE_CODE_115__ed components' programs (__INLINE_CODE_116__ functions) are lazily attached to already running upper-level Cycle.js program, so the __INLINE_CODE_117__ program has to be __INLINE_CODE_118__.
So, root __INLINE_CODE_119__, aside from __INLINE_CODE_120__ option, also needs __INLINE_CODE_121__ defined, so that it knows how to run your top-level Cycle.js program. This would normally be just a __INLINE_CODE_122__ function from __INLINE_CODE_123__ package (or __INLINE_CODE_124__, or __INLINE_CODE_125__, depending on the reactive streams library in use).
You could just __INLINE_CODE_126__ your top-level __INLINE_CODE_127__ component like we did in the first example:
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
...
function main(sources) {
...
}
function App() {
return (
// Some JSX
...
);
}
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
Though this will work, its advisable to create a specialized __INLINE_CODE_128__ component just for this purpose:
// RootCycle.js
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
function main(sources) {
...
}
export const RootCycle = cycleConnect(main, {
root: true,
runFn: run
})(); // No component to wrap? Its okay, read ahead
export default RootCycle;
The top-level Cycle sources are actually __INLINE_CODE_129__ and __INLINE_CODE_130__, so root Cycle is also in charge of providing these __INLINE_CODE_131__ and __INLINE_CODE_132__ to any Cycle.js program down the tree, as __INLINE_CODE_133__.
__INLINE_CODE_134__ option is the object with keys defining properties of the __INLINE_CODE_135__ object, identical to how you'd do that in a regular Cycle.js program.
__INLINE_CODE_136__ option accepts an array of wrappers (order might matter there, so its a user-land option).
Also, the __INLINE_CODE_137__ function is optional (because you might need a __INLINE_CODE_138__ to be a __INLINE_CODE_139__ or for __INLINE_CODE_140__ only), so you can skip it, unless you have some logic to implement there.
Note that the __INLINE_CODE_141__ can be passed to any __INLINE_CODE_142__, even non-root, but __INLINE_CODE_143__ option is only valid for __INLINE_CODE_144__ Cycle and is ignored otherwise.
// RootCycle.js
import { run } from '@cycle/run';
import { makeHTTPDriver } from '@cycle/http';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const drivers = { HTTP: makeHTTPDriver() };
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
export default RootCycle;
__INLINE_CODE_145__ component is basically in charge of bootstrapping the "Cycle" part of your app here.
Then just wrap your __INLINE_CODE_146__ with it and e.g., render with __INLINE_CODE_147__:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import RootCycle from './RootCycle';
ReactDOM.render(
(
<RootCycle>
<App />
</RootCycle>
),
document.getElementById('root')
);
__INLINE_CODE_148__ option
You might have noticed in axamples above that we're not providing any component to a __INLINE_CODE_149__ HOC, leaving empty braces instead:
const ConnectedComponent = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
Well, that is not a mistake - the wrapped component is optional. When a "component to wrap" is provided to __INLINE_CODE_150__, then if we render the connected component, the __INLINE_CODE_151__ will also render __INLINE_CODE_152__ internally.
Thats all good and works (and what you'll be using most of the time anyway), but sometimes you just want to keep your React Element tree a bit cleaner when the wrapped component would be a kind of noop anyway (just forwarding __INLINE_CODE_153__ and nothing else), or component is some technical/lower-level like __INLINE_CODE_154__, or have some special rendering behavior.
So, we want to avoid seeing this in React Developer Tools:
<cycleConnect(SomeComponent)>
<SomeComponent>
...
</SomeComponent>
</cycleConnect(SomeComponent)>
You can use __INLINE_CODE_155__ option for that. It accepts the function with this signature:
(props) => ReactNode | null
If no component is provided for wrapping, nor __INLINE_CODE_156__ option is defined, the __INLINE_CODE_157__ option defaults to __INLINE_CODE_158__ function, so its either forwarding __INLINE_CODE_159__ if any or renders nothing.
If both __INLINE_CODE_160__ option is provided and component-to-wrap is passed, then __INLINE_CODE_161__ takes the precedence.
Note: Why not use the __INLINE_CODE_162__ option in place of wrapped component all the time? Well, its technically possible, espeically if your React component is a stateless functional component anyway.
But regular HOC wrapping is just common technique in React world and is nicely composable when you have other HOCs than __INLINE_CODE_163__.
And is cleaner too - you might be prototyping your app as a set of __INLINE_CODE_164__ components first, without even involving __INLINE_CODE_165__, then "connect" them all at once, or introduce __INLINE_CODE_166__ incrementally. Whatever :)
__INLINE_CODE_167__ option
Your component's __INLINE_CODE_168__ is by default automatically assigned to the following (depending on whether the wrapped component is provided, or __INLINE_CODE_169__ option, or neither:
- __INLINE_CODE_170__ when WrappedComponent is passed
- __INLINE_CODE_171__ when __INLINE_CODE_172__ prop is defined (and is truthy)
- __INLINE_CODE_173__ otherwise
You can override that like with any regular React component:
const ConnectedComponent = cycleConnect({
...
)(Component);
ConnectedComponent.displayName = 'ConnectedComponent';
But there's also a __INLINE_CODE_174__ option to set the generated (connected) component __INLINE_CODE_175__, which looks a bit cleaner:
const ConnectedComponent = cycleConnect({
...
displayName: 'ConnectedComponent'
)(Component);
Inside the Cycle
__INLINE_CODE_176__ really assumes that you know how to use Cycle.js. Its really a very simple yet very elegant pattern, so make sure to familiarize yourself with it.
Quick recap on what Cycle.js __INLINE_CODE_177__ function is. Its basically a pure function with the signature:
main :: (Sources) => Sinks
It accepts __INLINE_CODE_178__ as an argument and returns __INLINE_CODE_179__ and describes a dataflow using composition of reactive streams and their transformations with pure functions.
Please note that the __INLINE_CODE_180__ function is only run once per __INLINE_CODE_181__ component lifecycle. It doesn't run again on rerendering or something. This is because the actual call to __INLINE_CODE_182__ is to just build up a dataflow, not to get some rendering data.
The data flows through reactive streams, from __INLINE_CODE_183__ to __INLINE_CODE_184__ of the __INLINE_CODE_185__, until its __INLINE_CODE_186__ are unsubscribed from parent Cycle program's __INLINE_CODE_187__ (this happens when React component is unmounted).
Drivers
The __INLINE_CODE_188__ is supposed to run side effects for a Cycle.js program. Its outside the program itself, supposed to be impure and connects your Cycle app with the "outer world". This is an essential concept and is described in a great detail in Cycle.js docs on drivers.
There are no differences from idiomatic Cycle.js in how __INLINE_CODE_189__ handles __INLINE_CODE_190__ - it just hands it over to __INLINE_CODE_191__ directly. There is only one __INLINE_CODE_192__ driver (an __INLINE_CODE_193__ driver) that is always added internally by the __INLINE_CODE_194__ed component - it allows to invoke functions outside the __INLINE_CODE_195__ that is to be maintained pure.
Wrappers
The __INLINE_CODE_196__ is a rather simple thing - its just a function that takes __INLINE_CODE_197__ function that wraps it with the other __INLINE_CODE_198__ function that might in turn add some logic in between:
function someWrapper(originalMain) {
return wrappedMain(sources) {
// Can do something here, like transforming `sources` somehow
// ...
// Invoke the original `main` internally to obtain its sinks
const sinks = originalMain(sources);
// Can do something to `originalMain`s sinks here
// ...
return {
...sinks,
someOtherSink: xs.of({ ... }) // Can add a new sink
};
}
}
It is important that the wrapping __INLINE_CODE_199__ is maintained pure, so that your entire program is pure. If you need some effectful logic to run in a wrapping __INLINE_CODE_200__, you should consider using driver for that. Wrappers, just like any regular __INLINE_CODE_201__ function, can internally use __INLINE_CODE_202__ that are created from __INLINE_CODE_203__.
Wrappers can also add arbitrary __INLINE_CODE_204__ and could expect certain __INLINE_CODE_205__ to be returned from the Cycle program, depending on particular wrapper implementation.
Wrappers are usually created to encapsulate some non-business-logic stuff, you'd be normally describe anything related to the app itself in the "original" __INLINE_CODE_206__ function.
Most of __INLINE_CODE_207__ (aside from __INLINE_CODE_208__ HOC itself) is implemented with wrappers, that are created for each __INLINE_CODE_209__ed component automatically:
- __INLINE_CODE_210__ (to mess around with React component props)
- __INLINE_CODE_211__ (a gateway to React component lifecycle streams)
- __INLINE_CODE_212__ (to route the view events back from your wrapped React component aka view, and pass the events upstream, to parent components)
Sources
This is a Cycle.js program's input. __INLINE_CODE_213__ function shouldn't care whether something on the __INLINE_CODE_214__ is a __INLINE_CODE_215__ or some custom thing provided by one of the wrappers.
For __INLINE_CODE_216__ connected component, the __INLINE_CODE_217__ are __INLINE_CODE_218__ directly plus whatever __INLINE_CODE_219__ add to them.
Sinks
This is a Cycle.js program's output.
__INLINE_CODE_220__ invokes your __INLINE_CODE_221__ed component's __INLINE_CODE_222__ function, takes its __INLINE_CODE_223__ and subscribes the __INLINE_CODE_224__ passed from the parent component to these __INLINE_CODE_225__. Its how __INLINE_CODE_226__ is "attaching" the new component to a tree of Cycle programs.
If there's no parent component (thus, no __INLINE_CODE_227__) and your component is a "Root Cycle", then the sinks are handled to __INLINE_CODE_228__ to "run" as with a regular Cycle.js app.
React __INLINE_CODE_229__
The __INLINE_CODE_230__ed __INLINE_CODE_231__ component is essentially a view for the associated Cycle.js __INLINE_CODE_232__ function. And __INLINE_CODE_233__ encourages the view to be a "dumb" stateless function that takes __INLINE_CODE_234__ as an argument ad returns something of __INLINE_CODE_235__ type (i.e., __INLINE_CODE_236__s, __INLINE_CODE_237__, etc, whatever __INLINE_CODE_238__ component renders).
Note: The wrapped __INLINE_CODE_239__ shouldn't necessarily be a stateless functional component (SFC), you could have any __INLINE_CODE_240__ component wrapped, but its advisable to use functions for the most part as the whole point of __INLINE_CODE_241__ is to use __INLINE_CODE_242__ for anything that is not a view.
So, the __INLINE_CODE_243__ is the only communication channel from our Cycle.js part (the connected component's __INLINE_CODE_244__ function) to connected __INLINE_CODE_245__ component itself. We therefore need to pass the props from our Cycle program to the view (a __INLINE_CODE_246__ component) somehow.
This is where __INLINE_CODE_247__ comes into play and provides __INLINE_CODE_248__ source and sink for a __INLINE_CODE_249__ function. Its local to a component, so no other components will share the same __INLINE_CODE_250__ source or sink.
__INLINE_CODE_251__ sink
The __INLINE_CODE_252__ sink on __INLINE_CODE_253__ed is there to pass the props to the view:
function main(sources) {
return {
props: xs.of({ greetable: 'World' })
};
}
// Use the passed `greetable` prop
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
Remember, that the value you map to __INLINE_CODE_254__ sink should be an object of new props and that updated props will be merged with existing component __INLINE_CODE_255__ like:
const updatedProps = Object.assign({}, prevProps, newProps);
This means that you can easily overwrite any props you want and leave the others intact.
Also, note above that we're passing the __INLINE_CODE_256__ through an __INLINE_CODE_257__ stream and that might be synchronous (like in our example with a stream of static data that is sent immediately) but could also be asynchronous. In that case the value of some prop might be not yet available on the first render:
function main(sources) {
return {
props: xs.of({ greetable: 'World' }).compose(delay(3000))
};
}
// Trying to use the passed `greetable` prop
// But whooops, no `greetable` yet :(
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
So either provide a default value for that async prop's __INLINE_CODE_258__ or perform additional checks in the view, when rendering.
Note: When messing with the __INLINE_CODE_259__ in your __INLINE_CODE_260__, remember that any props that are passed from outside as an input to a __INLINE_CODE_261__ component, are also there (including callback functions, if there are any) and are also merged upon updates from outside (on __INLINE_CODE_262__).
Lets summarize: the __INLINE_CODE_263__ from the __INLINE_CODE_264__ sink are passed from the __INLINE_CODE_265__ function to the view (__INLINE_CODE_266__ component).
__INLINE_CODE_267__ source
Not only you'll need to provide the props to the view for rendering, but oftentimes you'd need your __INLINE_CODE_268__ to take part in transformations/calculations in the __INLINE_CODE_269__ function itself. For that reason, __INLINE_CODE_270__ provides the __INLINE_CODE_271__ source as well.
It allows you to have an __INLINE_CODE_272__ stream of your component's input __INLINE_CODE_273__. Note that the __INLINE_CODE_274__ is of type __INLINE_CODE_275__ and to take the actual stream of props, use its __INLINE_CODE_276__ attribute:
function main(sources) {
const name$ = sources.props.props$.map(props => props.name);
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
function MyComponent(props) {
return (
<div>
Hello, {props.name}! Your father is Mr. {props.father}.
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
...
// Somewhere else:
<MyConnectedComponent name="Luke Skywalker" father="Darth Vader" />
// renders:
//
// <div>
// Hello, LUKE SKYWALKER! Your father is Mr. Darth Vader.
// </div>
The new value on __INLINE_CODE_277__ is emitted every time __INLINE_CODE_278__ change. This could be overkill for the most cases, as you would only need just few props for your dataflow.
This is what __INLINE_CODE_279__ method for, it accepts the name of the nested prop (will return __INLINE_CODE_280__ if the stream value is not an object) and returns a stream of that prop only that is only got new value once this particular prop changes:
function main(sources) {
const name$ = sources.props.pluck('name');
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
The __INLINE_CODE_281__ed stream also performs shallow comparison and does __INLINE_CODE_282__ behind the scenes, so identical values don't cause a new value emitted on an __INLINE_CODE_283__.
Note that you don't need to always pull all props from your __INLINE_CODE_284__ function's __INLINE_CODE_285__ just for the sake of it to just forward to the __INLINE_CODE_286__ sink directly, you better only pick those you need in a dataflow with __INLINE_CODE_287__. The __INLINE_CODE_288__ passed from outside will be available on the view normally. You can overwrite them from inside your __INLINE_CODE_289__ though.
There's also __INLINE_CODE_290__ method that also accepts a nested prop name. The difference is that the result is not an __INLINE_CODE_291__ stream of prop values but the __INLINE_CODE_292__ instance itself that is scoped to that piece of __INLINE_CODE_293__:
function main(sources) {
const name$ = sources.props.pluck('name'); // Stream of strings
const nameSource = sources.props.select('someNestedObject'); // Scoped ReactPropsSource
const someNestedObjProperty$ = nameSource.pluck('someNestedObjectProperty');
...
}
This is useful when you want to __INLINE_CODE_294__ a stream of some sub-prop from deeper nested objects.
Note: Taking deep property stream this way isn't really nice currently as requires quite a bit of boilerplate - this part of API is already being thought out further and reconsidered.
Interactions
So far so good, but rendering the __INLINE_CODE_295__ isn't the only exciting thing we want to do with __INLINE_CODE_296__, right? We also want to handle events coming from the view(e.g., the DOM).
The way __INLINE_CODE_297__ handles events and nested components' callbacks is the biggest deviation from an idiomatic Cycle.js app using __INLINE_CODE_298__ driver. It refrains from a holistic "no handlers in the view" approach for a few reasons:
- We need something more in line with __INLINE_CODE_299__ as we're using React here
- We're not always handling DOM events but also need to handle some children components data/events passed back. We can't pass streams like in regular Cycle.js app, in __INLINE_CODE_300__ we do it via callbacks that are provided amongst children components' __INLINE_CODE_301__ and we need __INLINE_CODE_302__ed components to be compatible __INLINE_CODE_303__ components at all levels.
This is what "interactions" feature of __INLINE_CODE_304__ is for. It provides the unified interface for parent-child components communication and event handling that is aligned with a regular event/callback handling model on __INLINE_CODE_305__ side but also allows to access those events in form of __INLINE_CODE_306__ streams inside the __INLINE_CODE_307__ function.
Events triggering by the view
Every __INLINE_CODE_308__ component has some additional __INLINE_CODE_309__-specific props injected, so they are available on your view.
These are __INLINE_CODE_310__ which is a function that takes the __INLINE_CODE_311__ (a string) and optional __INLINE_CODE_312__ (of any type) and returns a function that will be a callback for your event or some child component prop function.
So, the signature is:
(interactionType, predefinedValue?) => (value) => void
Its supposed to be called imperatively in your view and is available as __INLINE_CODE_313__:
function MyComponent({ interact }) {
return (
<div>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
For the sake of being cleaner (and for better TypeScript support), __INLINE_CODE_314__ also leverages the ES2015 __INLINE_CODE_315__ feature to provide the object with dynamic properties, each representing an __INLINE_CODE_316__ with __INLINE_CODE_317__ dynamically defined after the function property name. Its available on __INLINE_CODE_318__ so we can rewrite the above snippet as:
function MyComponent({ interactions }) {
return (
<div>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
In the example above, the __INLINE_CODE_319__ function receives the click __INLINE_CODE_320__ as an argument and that is what you'll get in your __INLINE_CODE_321__ (next section).
While cleaner, it doesn't allow for the __INLINE_CODE_322__ to be set, so if you need some particular value to be emitted by the associated stream in your __INLINE_CODE_323__, you can either use __INLINE_CODE_324__ or create an inline function:
// Assuming you have `id` available, e.g.
// when looping/mapping over the collection of items
<button
type="button"
className="delete-btn"
onClick={interact('delete', id)}
>
Click me!
</button>
// OR
<button
type="button"
className="delete-btn"
onClick={() => interactions.delete(id)}
>
Click me!
</button>
View events handling with __INLINE_CODE_325__ source
There's internal __INLINE_CODE_326__ in __INLINE_CODE_327__ and it provides the __INLINE_CODE_328__ source for our __INLINE_CODE_329__ function. The __INLINE_CODE_330__ source is of type __INLINE_CODE_331__, which has the __INLINE_CODE_332__ method, which we can use to obtain interaction streams:
function main(sources) {
const buttonClick$ = sources.interactions.select('buttonClick');
// Do something with a buttonClick$, e.g.
// map it to the click coordinates props
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interact, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
The streams are __INLINE_CODE_333__ed by the same __INLINE_CODE_334__ string you provided to the __INLINE_CODE_335__ function on your view.
Similar to how __INLINE_CODE_336__ object with dynamic function-props is available on your view, __INLINE_CODE_337__ is a ES2015 __INLINE_CODE_338__ and it maintains the dynamic streams right on itself (just don't use __INLINE_CODE_339__ as its reserved method name here here):
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interactions, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
Note: If you try to get the non-existing stream, it will still work, this thing is dynamic, so the stream will be created nevertheless but will just never emit values. Be careful and look out for typos! Using TypeScript is highly recommended to avoid typos here.
Upstream interactions with __INLINE_CODE_340__ sink (calling callbacks on __INLINE_CODE_341__).
Now that we can handle the streams of view events and values from the children components, we would also need to somehow call the callbacks that are passed among our __INLINE_CODE_342__.
First, nothing prevents from following the common React way and call those directly from the view:
function MyComponent({ onButtonClick }) {
return (
<button type="button" onClick={onButtonClick}>
Click me!
</button>
);
}
// Somewhere else:
doSomething = (event) => { console.log('clicked!'); };
...
<MyComponent onButtonClick={doSomething} />
This is fine if you don't need that value's stream in your dataflow. This approach bypasses your Cycle.js program's __INLINE_CODE_343__ completely and doesn't make it impure by otherwise possibly passing an effectful function as __INLINE_CODE_344__. There's even no need to __INLINE_CODE_345__ your component in the above example.
Keep in mind that if __INLINE_CODE_346__ is optional there, you will also need to check whether that is defined before providing that as a callback (use __INLINE_CODE_347__ or TypeScript to mark it as either required or optional).
Oftentimes you will need to call some callback that lives on the __INLINE_CODE_348__ though, and this is what __INLINE_CODE_349__ sink provided by __INLINE_CODE_350__ does:
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
const coords$ = buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
);
// We both update the `props` (to render it in this component)
// and pass upstream
return {
props: coords$,
interactions: xs.of({
onCoordsChange: coords$
})
};
}
function MyComponent({ interactions }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
handleCoordsChange = (coords) => {
console.log('New coordinates received: ', coords);
};
...
<ConnectedComponent onCoordsChange={handleCoordsChange} />
// The new coords will be rendered by the component
// and the console will show the "New coordinates received"
// with new coords too.
Once the appropriate stream emits a value, that callback is called. It will also check if the callback was passed in the first place to skip the call attempt if no callback with a given name is passed among the __INLINE_CODE_351__.
Important: Note that __INLINE_CODE_352__ sink expects a stream of object whose property names correspond to callback names on __INLINE_CODE_353__ and property values are themselves streams too - whatever values those streams emit, will be passed as an argument to the callback.
So far so good, but we know that our __INLINE_CODE_354__ is a wrapper and not a driver, so what if one passed the effectful function as a callback, e.g., __INLINE_CODE_355__ passed as __INLINE_CODE_356__ above does a __INLINE_CODE_357__ internally - that is a side effect, right?
Well, this is why the function is not invoked directly, or inside the wrapper (since the wrapper is the part of the pure land of our __INLINE_CODE_358__s), its using the __INLINE_CODE_359__ driver internally. More on this below in a section about Side-effect control, just keep in mind that __INLINE_CODE_360__ and __INLINE_CODE_361__ should be pure and __INLINE_CODE_362__ follows that requirement.
React component lifecycle streams
You're probably not going to need __INLINE_CODE_363__ component lifecycle hooks often inside your __INLINE_CODE_364__, but still - they are provided via special __INLINE_CODE_365__. It provides the __INLINE_CODE_366__ source that is available on each __INLINE_CODE_367__ed __INLINE_CODE_368__ sources.
The lifecycle events are provided as __INLINE_CODE_369__ streams and their names mostly mirror the React lifecycle methods, but shortened to remove some redundant __INLINE_CODE_370__ prefixes:
React lifecycle method
__INLINE_CODE_371__ source property
Stream value
__INLINE_CODE_372__
__INLINE_CODE_373__
__INLINE_CODE_374__
__INLINE_CODE_375__
__INLINE_CODE_376__
__INLINE_CODE_377__
__INLINE_CODE_378__
__INLINE_CODE_379__
__INLINE_CODE_380__
__INLINE_CODE_381__
__INLINE_CODE_382__
__INLINE_CODE_383__
__INLINE_CODE_384__
__INLINE_CODE_385__
__INLINE_CODE_386__
__INLINE_CODE_387__
__INLINE_CODE_388__
__INLINE_CODE_389__
__INLINE_CODE_390__
__INLINE_CODE_391__
Error data
The "error data" on the __INLINE_CODE_392__ is an object of this shape:
{
error: Error;
errorInfo: ErrorInfo;
}
These are basically React __INLINE_CODE_393__ arguments squashed into a single object.
Also, note that __INLINE_CODE_394__ hook is not present there, since that is not really an event and affects how your component is rendered. __INLINE_CODE_395__ would be providing its own custom internal __INLINE_CODE_396__ instead for optimisation purposes and you could implement it on your wrapped component anyway.
Note: As well, no lifecycle events are passing anything related to React component __INLINE_CODE_397__, only taking __INLINE_CODE_398__ into account instead. This is because the __INLINE_CODE_399__ is meant to be managed differently when Cycle.js is in use. You can still use that for wrapped component but its out of play on the component generated by __INLINE_CODE_400__.
A quick example:
function main(sources) {
const willUnmount$ = { sources.lifecycle };
// Do something on unmounting, e.g. report that to a console
// via some hypothetical `logger` driver
return {
logger: willUnmount$.mapTo(`I'll be unmounted soon!`)
};
}
Side-effect control and __INLINE_CODE_401__ driver
Side-effect control is where Cycle.js really shines (not only implementation, it applies to the "cycle pattern" behind Cycle.js in the first place).
In order to keep the __INLINE_CODE_402__ part of __INLINE_CODE_403__ed component pure, we need to make sure that any potentially effectful function is not run inside our __INLINE_CODE_404__ directly.
This is what __INLINE_CODE_405__ driver is responsible for. It allows to only describe what function you want to call and with what arguments, then delegate that to a __INLINE_CODE_406__ which is supposed to run any impure functions outside of the app itself.
The way it works is very similar to that of __INLINE_CODE_407__ driver from __INLINE_CODE_408__ package where you don't actually run the request inside your __INLINE_CODE_409__ but just describe it and pass to __INLINE_CODE_410__ driver to handle. __INLINE_CODE_411__ driver is just a bit more generic and operates on functions.
__INLINE_CODE_412__ uses __INLINE_CODE_413__ driver internally (to run effects for upstream __INLINE_CODE_414__), but it is also available for you on any connected __INLINE_CODE_415__ as __INLINE_CODE_416__ sink automatically (i.e., without explicit configuration on a root cycle):
function log() {
console.log(`I'm effectful!`);
}
function saveToLocalStorage(message) {
localStorage.setItem('message', message);
}
function main(sources) {
const someCallbackFn$ = sources.props.pluck('someCallbackFunction');
return {
fnCallEffect: xs.merge(
xs.of({ fn: log }),
xs.of({ fn: saveToLocalStorage, args: ['some message to save'] }),
// Call the `props` callback function directly, this is how
// `interactions` sink works behind the scenes:
someCallbackFn$.map(fn => ({ fn }))
)
};
}
Isolation
Isolation is Cycle.js way of making the component's __INLINE_CODE_417__ and __INLINE_CODE_418__ isolated from influence of other components.
Make sure to carefully read more about Cycle.js isolation here. It might take a bit to get used to an idea.
To reiterate: the concept of isolation is important for Cycle.js apps. Its a little bit different for __INLINE_CODE_419__ because most things are scoped to a particular __INLINE_CODE_420__ed component already, like components have "local" __INLINE_CODE_421__, __INLINE_CODE_422__ and __INLINE_CODE_423__ sources and sinks and __INLINE_CODE_424__ components are also self-contained already.
There are though __INLINE_CODE_425__/__INLINE_CODE_426__ that will need the isolation feature, like __INLINE_CODE_427__ driver or __INLINE_CODE_428__ wrapper (a state management solution for Cycle.js apps). So, isolation makes sense for shared __INLINE_CODE_429__/__INLINE_CODE_430__, those you pass to __INLINE_CODE_431__ function (by defining the __INLINE_CODE_432__/__INLINE_CODE_433__ options on root __INLINE_CODE_434__) and that will be available across the entire tree of Cycle programs.
Note: The isolation works by scoping the source streams to particular component and a kind of unscoping of the sink streams before connecting to parent's sinks.
Also note, that each driver/wrapper decides on its own how its sources/sinks should be isolated.
In Cycle.js, you use the __INLINE_CODE_435__ function from __INLINE_CODE_436__ package directly, as you're just invoking the children __INLINE_CODE_437__ functions from your current component's __INLINE_CODE_438__ function:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// Do something with childSinks, combine somehow, etc
return {
...
};
}
__INLINE_CODE_439__ function has this simple signature:
isolate :: (MainFn, scope) => MainFn
It takes the __INLINE_CODE_440__ function and an optional __INLINE_CODE_441__ and returns an isolated __INLINE_CODE_442__ function.
__INLINE_CODE_443__ does also use the __INLINE_CODE_444__ package internally, but you can't call __INLINE_CODE_445__ directly on some __INLINE_CODE_446__ function. You'd never call one component's __INLINE_CODE_447__ from the other component's __INLINE_CODE_448__ directly with __INLINE_CODE_449__.
Naive explicit isolation (conceptually incorrect)
Just to illustrate the issue, there's an attempt to isolate the component's __INLINE_CODE_450__ sources, so that HTTP requests initiated from that components are scoped to it (and are unavailable to siblings).
Since we know that __INLINE_CODE_451__ returns a __INLINE_CODE_452__ function and __INLINE_CODE_453__ takes the __INLINE_CODE_454__ function as an argument, lets try to feed it an isolated version of it:
function main(sources) {
...
}
function MyComponent() {
...
}
const isolatedMain = isolate(main, { HTTP: 'myComponent' });
const ConnectedComponent = cycleConnect(isolatedMain)(MyComponent);
While this will won't raise any errors (because this is valid code after all), this approach brings a subtle issue.
This will isolate things for this particular components but not their children components, so in this case, children (connected childred __INLINE_CODE_455__ components) will have no __INLINE_CODE_456__ isolation scope for __INLINE_CODE_457__ channel.
This might seems like a no-issue, because hey, you can define different scopes for the children components right? Well, while this is technically possible, its conceptually incorrect and it will be completely broken in some cases, e.g., with __INLINE_CODE_458__ where the deeper you go - the "more filtered" source streams you get.
Important! The above note about it being "conceptually incorrect" refers to the way __INLINE_CODE_459__ maintains the tree of Cycle.js programs internally. It tries to make the entire app structure look the same way as you'd manually write a Cycle.js app. So, when you __INLINE_CODE_460__ at some level, then if inside your __INLINE_CODE_461__ you also call some __INLINE_CODE_462__ happens, then __INLINE_CODE_463__ gets isolated sources no matter what.
__INLINE_CODE_464__ option for __INLINE_CODE_465__
The first way to correctly isolate __INLINE_CODE_466__ed component is to use the __INLINE_CODE_467__ option for __INLINE_CODE_468__ itself, so the above can be rewritten as:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main, {
isolate: { HTTP: 'myComponent' }
})(MyComponent);
This will work perfectly, with all children __INLINE_CODE_469__ed components having __INLINE_CODE_470__ namespace applied to __INLINE_CODE_471__ sources/sinks in their __INLINE_CODE_472__ functions.
The __INLINE_CODE_473__ option accepts a __INLINE_CODE_474__ or the __INLINE_CODE_475__ and in that case its passed directly to __INLINE_CODE_476__ package's __INLINE_CODE_477__ as a __INLINE_CODE_478__ argument. So, check out the __INLINE_CODE_479__ function API reference.
__INLINE_CODE_480__ also makes it accept a function that takes component __INLINE_CODE_481__ as an argument and should return the scope of __INLINE_CODE_482__ or __INLINE_CODE_483__ type that is then also passed to __INLINE_CODE_484__ package's __INLINE_CODE_485__. This allows for some dynamic isolation scopes to be set based on the React component's initial props.
Important: Despite the __INLINE_CODE_486__ is provided as an option to __INLINE_CODE_487__ when defining the connected component, the actual isolation will only happen when component instance is created. This is why its possible to dynamically resolve the __INLINE_CODE_488__ based on the component __INLINE_CODE_489__ in __INLINE_CODE_490__.
__INLINE_CODE_491__ prop
Despite the __INLINE_CODE_492__ option will work nicely for some cases, there's a pattern in how it is utilized in pure Cycle.js programs. Lets take a look at pure Cycle.js example again:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// ...
return {
...
};
}
The important part is that in Cycle.js app its not a child who decides how to isolate itself but __INLINE_CODE_493__ is provided from the parent instead.
Most of the time, we want to maintain the same approach with __INLINE_CODE_494__. The advantage is that component in this case is totally agnostic from the provided scope, it doesn't even care whether its isolated or not at all - all it knows is that it expects certain __INLINE_CODE_495__ and returns certain __INLINE_CODE_496__. This makes the component reusable.
This is especially important with __INLINE_CODE_497__, where you want to control where your child components "attach" to the state.
In other words, the parent decides how to compose children components, not the other way around, because otherwise the child would need to have an intimate knowledge of something that really belongs to parent and thus not being reusable and breaking encapsulation.
For this reason, every __INLINE_CODE_498__ed React component accepts an __INLINE_CODE_499__ prop, so you can use that just like any other prop:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
// Isolate everything
<ConnectedComponent isolate="myComponent" />
// Isolate HTTP only
<ConnectedComponent isolate={{ HTTP: 'myComponent' }} />
<ConnectedComponent isolate={{ HTTP: 'myComponent_2' }} />
In contrast with __INLINE_CODE_500__ option, the __INLINE_CODE_501__ prop can only accept __INLINE_CODE_502__ or __INLINE_CODE_503__ that is directly passed to __INLINE_CODE_504__ function from __INLINE_CODE_505__, thus maintaining full parity with pure Cycle.js approach. Indeed, having a function that accepts initial __INLINE_CODE_506__ wouldn't make much sense because you're anyway providing props that could be different for different component instances when rendering that component.
__INLINE_CODE_507__ helper component
Since isolation in __INLINE_CODE_508__ applies to both "current" __INLINE_CODE_509__ed component and all its __INLINE_CODE_510__ed children, we might sometimes need to define some connected component to only be a common parent for some children, which must provide isolation but wouldn't contain any logic.
This is what a helper __INLINE_CODE_511__ component for. It accepts the __INLINE_CODE_512__ prop (that is directly passed as a __INLINE_CODE_513__ argument to __INLINE_CODE_514__ function of __INLINE_CODE_515__):
<Isolator scope={{ HTTP: 'someNamespace' }}>
<ConnectedComponent someProps="1" />
<ConnectedComponent someProps="2" />
<ConnectedComponent someProps="3" />
</Isolator>
This assists the isolation composition at view level.
Extra: State management with __INLINE_CODE_516__
__INLINE_CODE_517__ is the idiomatic Cycle.js fractal state management solution. It is tailored for Cycle.js apps but since __INLINE_CODE_518__ is using the real Cycle.js, it stays fully compatible here as well.
Using __INLINE_CODE_519__ with __INLINE_CODE_520__ is optional, since you might be using different lib (e.g., __INLINE_CODE_521__) for that.
To start, just read the onionify docs and install the package:
$ npm install --save cycle-onionify
and pass that with __INLINE_CODE_522__ array to a __INLINE_CODE_523__ connected component (e.g., Root Cycle):
// RootCycle.js
import { run } from '@cycle/run';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
wrappers
})();
export default RootCycle;
Now every __INLINE_CODE_524__ed component that is a child of the above-mentioned __INLINE_CODE_525__ in React component tree got the __INLINE_CODE_526__ source and sink.
Important: Notice that you're all set - its purely a Cycle.js land. __INLINE_CODE_527__ doesn't provide any additional functionality for __INLINE_CODE_528__. Instead, it provides few helpers for isolation and working with collections on the React side.
All the exportables related to __INLINE_CODE_529__ reside in the extra package: __INLINE_CODE_530__. It is installed along with the main lib but is not __INLINE_CODE_531__d/__INLINE_CODE_532__ed by default, so won't be bundled (by Webpack, Rollup, etc) unless you're using it.
This extra sub-package contains few things:
- __INLINE_CODE_533__ helper component
- __INLINE_CODE_534__ helper component
- __INLINE_CODE_535__ helper factory function
Read ahead for details on each.
In __INLINE_CODE_536__, the state/reducers layer composition is done via isolation, so the first thing here is __INLINE_CODE_537__:
__INLINE_CODE_538__ helper component
This is pretty much like __INLINE_CODE_539__ but is here to specifically isolate __INLINE_CODE_540__ sources/sinks only.
So, the:
import { StateIsolator } from 'react-cycle-connect/lib/extra/onionify';
// ...
<StateIsolator lens="someStateProperty">
<NestedComponent />
</StateIsolator>
is somewhat a convenient shortcut to:
import { Isolator } from 'react-cycle-connect';
// ...
<Isolator scope={{ onion: 'someStateProperty', '*': null }}>
<NestedComponent />
</Isolator>
Notice how __INLINE_CODE_541__'s prop is explicitly named __INLINE_CODE_542__. It just implies that oftentimes you'll be using "lens" to isolate the state. Consider a string just a shortcut to a more elaborate __INLINE_CODE_543__ object.
In the example above, the __INLINE_CODE_544__'s __INLINE_CODE_545__ function __INLINE_CODE_546__ will represent only that "someStateProperty" piece of state. Pretty much how you'd __INLINE_CODE_547__ the __INLINE_CODE_548__ channel in an idiomatic Cycle.js app.
There's also a __INLINE_CODE_549__ property, so that you can change the name of the source/sink inside your __INLINE_CODE_550__ to something else. Thats to be on par with __INLINE_CODE_551__ features, where you can also change that name.
Handling collection state manually
Managing collections of components in pure Cycle.js with __INLINE_CODE_552__ is a kind of a recognized challenge. This is why __INLINE_CODE_553__ ended up providing __INLINE_CODE_554__ helper function - see their docs for more detail.
While __INLINE_CODE_555__ also has a __INLINE_CODE_556__ helper component that is very similar to __INLINE_CODE_557__ __INLINE_CODE_558__ function, lets first try to pretend we don't have it and imagine how'd we handle the state without it.
Note: Nothing prevents you from skipping right to a description of how __INLINE_CODE_559__ works, but it might be useful to understand the rationale behind it, so reading this section is recommended.
Handling collections is different with React than in Cycle.js, e.g., you can always just render React components in a loop or __INLINE_CODE_560__ an array of data to an array of components and React will render that.
When you want to render some __INLINE_CODE_561__ data from your state, its easy without any helpers:
/*
Assume you have this value on your `List` component `stateFork
- updated to more recent version of cycle, including @cycle/state
- updated to React 16.9 api, including new lifecycles and context api
- merged onionify separate lib so import is __INLINE_CODE_0__
- added tests
- published at @iilab/react-cycle-connect
react-cycle-connect
An experimental library that allows using Cycle.js as a reactive dataflow framework for React.
Written in TypeScript.
This little thing allows to define your app's view layer using regular React components, but implement the actual logic behind it using pure functional reactive approach, taking advantage of Cycle.js' Model-View-Intent pattern and its exceptional side effect control.
This isn't a custom implementation of Cycle pattern for __INLINE_CODE_1__ but a link to real Cycle.js instead, so any Cycle.js drivers and wrappers can be applied (e.g., __INLINE_CODE_2__ for state management). Also, for this reason, some knowledge about how __INLINE_CODE_3__ works is assumed.
Note: This is an experimental lib and currently works with __INLINE_CODE_4__ and __INLINE_CODE_5__ only. If all goes good, the support for __INLINE_CODE_6__/__INLINE_CODE_7__ (instead of __INLINE_CODE_8__) and for __INLINE_CODE_9__/__INLINE_CODE_10__ (instead of __INLINE_CODE_11__) is anticipated. Nothing prevents from having __INLINE_CODE_12__ covered too.
UPDATE (MAR 2018): Some internals overhaul is pending and it's currently waiting for Cycle.js to settle on hybrid push-pull reactivity and have internals rewritten to use Callbags instead of __INLINE_CODE_13__. So, __INLINE_CODE_14__ is going to be using Callbags by default too.
Any feedback is highly appreciated!
Table of Contents
- react-cycle-connect
- Table of Contents
- Shup up and show me the code
- Installation
- Motivation
- How it works (architecture)
- Guide
- API reference
- Alternatives / Competitors
- FAQ
- Roadmap
Shup up and show me the code
Cycle.js's counter example ported over:
import React from 'react';
import ReactDOM from 'react-dom';
import xs from 'xstream';
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
// Your Cycle.js `main` function
function main(sources) {
const action$ = xs.merge(
sources.interactions.decrement.map(ev => -1),
sources.interactions.increment.map(ev => +1)
);
const count$ = action$.fold((acc, x) => acc + x, 0);
return {
props: count$.map(count => ({ count }))
};
}
// Your React component
function App({ interactions, count }) {
return (
<div>
<button onClick={interactions.decrement}>Decrement</button>
<button onClick={interactions.increment}>Increment</button>
<p>Counter: {count}</p>
</div>
);
}
// Some connection magic
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
// Render normally as it's a regular React app
ReactDOM.render(
<ConnectedApp />,
document.getElementById('root')
);
Also - there's official TodoMVC example with __INLINE_CODE_33__ for state management.
Installation
Using npm:
$ npm install --save react-cycle-connect
Using yarn:
$ yarn add react-cycle-connect
Motivation
The motivation to create this lib was based off the need to retain the React-style composability of the view layer using regular React components, reuse existing React components, make it (the lib) easily/incrementally adoptable in an ongoing React project, but also take advantage of the Cycle.js' MVI pattern and side effect control.
At the same time, while the most valuable thing from __INLINE_CODE_34__ is the pattern itself, there are numerous available libs/plugins for Cycle.js to take advantage of, letting us take the best of both worlds.
This way, you can still use anything from React stack (e.g., __INLINE_CODE_35__ to compose your routed app screens), but describe the interactions as a dataflow, with pure functions and reactive streams. Most of React components are going to be "dumb" (and also stateless functional aka SFC) since all the backing logic and cross-component communication would be handled by a tree of __INLINE_CODE_36__ programs.
Why not just use Cycle.js alone?
That is what I'd recommend considering in the first place. Its fast, its cool, the __INLINE_CODE_37__ (virtual DOM rendering engine behind Cycle's DOM driver) is goddamn fast and reliable. If you don't intend to use __INLINE_CODE_38__ components in any way, don't even get bothered. The __INLINE_CODE_39__ is more about "introducing Cycle.js to __INLINE_CODE_40__", not the other way around.
Is it a "Cycle.js in React" or "React in Cycle.js"?
More of a "Cycle.js in React". The app is bootstrapped by rendering a React component to the DOM, view is composed the React way. The root Cycle.js program is being __INLINE_CODE_41__ by rendering a root __INLINE_CODE_42__ed component. Though, it doesn't prescribe how you write your Cycle.js programs, and aside from the DOM generation part, its just Cycle.js all the way.
How it works (architecture)
Every __INLINE_CODE_43__ed component got a real Cycle.js program "attached" to it. The boundary between the two is __INLINE_CODE_44__. From your Cycle.js program standpoint, __INLINE_CODE_45__ is a view. Cycle.js program can update __INLINE_CODE_46__ that React component will then render.
A bunch of nested React components in your app represent a "tree of React components". __INLINE_CODE_47__ maintains a "tree of Cycle.js programs" that map to __INLINE_CODE_48__ed components and takes advantage of React's context feature to provide links between the nodes. Each connected component provides scoped links to the children nodes .
New "Cycle nodes" are attached lazily, upon associated React component initialization (this is where React controls how Cycle programs are run). The above-mentioned "link" is just an object on React __INLINE_CODE_49__ that brings couple properties:
- __INLINE_CODE_50__ for the child connected component's __INLINE_CODE_51__ function; and
- __INLINE_CODE_52__ to subscribe to that component's __INLINE_CODE_53__ function __INLINE_CODE_54__.
This is where something unusual for the Cycle.js happens - we subscribe to and unsubscribe from connected component's __INLINE_CODE_55__ function __INLINE_CODE_56__ based on React component's lifetime hooks. In Cycle.js, you normally just run the app at startup and let it run as an ongoing process. But this is where __INLINE_CODE_57__ performs the sync between the two worlds.
Overall, aside from using React __INLINE_CODE_58__ (which is just an implementation detail) and some intense wrapping on the __INLINE_CODE_59__ side to make laziness possible, __INLINE_CODE_60__ tries to represent the entire tree of Cycle.js programs as close as possible to what you'd end up with by writing idiomatic Cycle.js with explicit calling of nested component's __INLINE_CODE_61__ functions from the parent component __INLINE_CODE_62__ function. The result of children components is always __INLINE_CODE_63__d.
Aside from passing the __INLINE_CODE_64__ to children components, __INLINE_CODE_65__ also explicitly applies some internal wrappers to integrate with React component: __INLINE_CODE_66__, __INLINE_CODE_67__ and __INLINE_CODE_68__, that are scoped to a connected component's Cycle.js program only. It also explicitly runs the internal __INLINE_CODE_69__ driver for the __INLINE_CODE_70__ nodes by default - this is necessary for the __INLINE_CODE_71__ sink to work (because its supposed to run callbacks passed via __INLINE_CODE_72__ and that might be an effectful function).
Note that the entire internal "tree" of Cycle.js programs is maintained pure, including internal component-scoped wrappers, just as you would do with regular Cycle.js program.
Differences from idiomatic Cycle.js usage
Note: there's a difference in how'd you write the __INLINE_CODE_73__/__INLINE_CODE_74__ app vs pure __INLINE_CODE_75__ app. There's no misuse of Cycle.js per se, we just don't use it for rendering with __INLINE_CODE_76__ - and that applies certain implications to the approach.
__INLINE_CODE_77__ and __INLINE_CODE_78__, while both allow to create user interfaces, imply different approaches to the app architecture. The most important part here is that they are conceptually different when it comes to generating the VDOM.
In order to combine the two approaches nicely, there are certain deviations from how'd you write the __INLINE_CODE_79__-only app to allow for more optimal React DX.
The main difference from a pure __INLINE_CODE_80__ way is that its not a Cycle program that technically generates VDOM - instead, there's React component to represent a piece of UI. __INLINE_CODE_81__ approach to view/__INLINE_CODE_82__ event handling is rather purist - view only defines the markup and nothing else (i.e., user interaction/event handlers are defined inside Cycle program itself and not as some view component __INLINE_CODE_83__/etc attribute). That is not a crazy random opinion but instead a very well thought and weighted out decision by Cycle.js team. __INLINE_CODE_84__ takes a different approach, which isn't that strict and is more in line with React, with callbacks amongst __INLINE_CODE_85__ and all that stuff. It enables the events/component callbacks handling via interactions.
The view composition is obviously completely different. In __INLINE_CODE_86__ you run each component's __INLINE_CODE_87__ function that (possibly) generates a VDOM node and then explicitly combine the pieces of markup into a single VDOM stream that is then given to a DOM driver. With __INLINE_CODE_88__ you compose the view layer with React components normally. You can just connect those to Cycle.js program to "outsource" the actual logic to it.
Guide
This assumes the preliminary knowledge of both __INLINE_CODE_89__ and __INLINE_CODE_90__ (and __INLINE_CODE_91__). It doesn't provide much of the insight on how these work, but is instead focused on how __INLINE_CODE_92__ combines the two.
Basics
The result of __INLINE_CODE_93__ call is a HOC that optionally takes a React component (similar to that __INLINE_CODE_94__ function of __INLINE_CODE_95__) and returns a component that is augmented with backing Cycle.js program. This Cycle.js program is internally wrapped into a "Cycle Node" that, when initialized, is connected to a tree of Cycle.js programs. "Cycle Nodes" of every __INLINE_CODE_96__ed components are linked together via React __INLINE_CODE_97__.
Note, though, that there are no direct references from one component's Cycle program to another component's Cycle program, i.e., you're not invoking one __INLINE_CODE_98__ function from the other (this is perfectly possible though, but since we're not using __INLINE_CODE_99__ here, it would only handle non-DOM stuff). Instead, __INLINE_CODE_100__ handles the linking behind the scenes, so the "sibling" Cycle programs are fed with the same __INLINE_CODE_101__ and subscribe their __INLINE_CODE_102__ to the same __INLINE_CODE_103__.
There's essential concept of isolation in Cycle.js, so every __INLINE_CODE_104__ed component accepts an __INLINE_CODE_105__ prop, that is directly passed to __INLINE_CODE_106__ function from __INLINE_CODE_107__ package. Read more about Cycle.js isolation here.
Root Cycle
Every __INLINE_CODE_108__ed component provides a __INLINE_CODE_109__ object to its children through React __INLINE_CODE_110__ and also expects that __INLINE_CODE_111__ from some parent component (not necessarily immediate parent).
That link object has the following shape:
{
// Sources to provide to our Cycle.js program's `main` function as an argument
sources: { ... },
// sinkProxies to subscribe to our Cycle program's `main` function sinks
sinkProxies: { ... }
}
You shouldn't be working with that directly but its important that something needs to provide the top-level __INLINE_CODE_112__ and __INLINE_CODE_113__ in the first place. This is what __INLINE_CODE_114__ option of cycle connect for.
Also, the __INLINE_CODE_115__ed components' programs (__INLINE_CODE_116__ functions) are lazily attached to already running upper-level Cycle.js program, so the __INLINE_CODE_117__ program has to be __INLINE_CODE_118__.
So, root __INLINE_CODE_119__, aside from __INLINE_CODE_120__ option, also needs __INLINE_CODE_121__ defined, so that it knows how to run your top-level Cycle.js program. This would normally be just a __INLINE_CODE_122__ function from __INLINE_CODE_123__ package (or __INLINE_CODE_124__, or __INLINE_CODE_125__, depending on the reactive streams library in use).
You could just __INLINE_CODE_126__ your top-level __INLINE_CODE_127__ component like we did in the first example:
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
...
function main(sources) {
...
}
function App() {
return (
// Some JSX
...
);
}
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
Though this will work, its advisable to create a specialized __INLINE_CODE_128__ component just for this purpose:
// RootCycle.js
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
function main(sources) {
...
}
export const RootCycle = cycleConnect(main, {
root: true,
runFn: run
})(); // No component to wrap? Its okay, read ahead
export default RootCycle;
The top-level Cycle sources are actually __INLINE_CODE_129__ and __INLINE_CODE_130__, so root Cycle is also in charge of providing these __INLINE_CODE_131__ and __INLINE_CODE_132__ to any Cycle.js program down the tree, as __INLINE_CODE_133__.
__INLINE_CODE_134__ option is the object with keys defining properties of the __INLINE_CODE_135__ object, identical to how you'd do that in a regular Cycle.js program.
__INLINE_CODE_136__ option accepts an array of wrappers (order might matter there, so its a user-land option).
Also, the __INLINE_CODE_137__ function is optional (because you might need a __INLINE_CODE_138__ to be a __INLINE_CODE_139__ or for __INLINE_CODE_140__ only), so you can skip it, unless you have some logic to implement there.
Note that the __INLINE_CODE_141__ can be passed to any __INLINE_CODE_142__, even non-root, but __INLINE_CODE_143__ option is only valid for __INLINE_CODE_144__ Cycle and is ignored otherwise.
// RootCycle.js
import { run } from '@cycle/run';
import { makeHTTPDriver } from '@cycle/http';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const drivers = { HTTP: makeHTTPDriver() };
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
export default RootCycle;
__INLINE_CODE_145__ component is basically in charge of bootstrapping the "Cycle" part of your app here.
Then just wrap your __INLINE_CODE_146__ with it and e.g., render with __INLINE_CODE_147__:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import RootCycle from './RootCycle';
ReactDOM.render(
(
<RootCycle>
<App />
</RootCycle>
),
document.getElementById('root')
);
__INLINE_CODE_148__ option
You might have noticed in axamples above that we're not providing any component to a __INLINE_CODE_149__ HOC, leaving empty braces instead:
const ConnectedComponent = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
Well, that is not a mistake - the wrapped component is optional. When a "component to wrap" is provided to __INLINE_CODE_150__, then if we render the connected component, the __INLINE_CODE_151__ will also render __INLINE_CODE_152__ internally.
Thats all good and works (and what you'll be using most of the time anyway), but sometimes you just want to keep your React Element tree a bit cleaner when the wrapped component would be a kind of noop anyway (just forwarding __INLINE_CODE_153__ and nothing else), or component is some technical/lower-level like __INLINE_CODE_154__, or have some special rendering behavior.
So, we want to avoid seeing this in React Developer Tools:
<cycleConnect(SomeComponent)>
<SomeComponent>
...
</SomeComponent>
</cycleConnect(SomeComponent)>
You can use __INLINE_CODE_155__ option for that. It accepts the function with this signature:
(props) => ReactNode | null
If no component is provided for wrapping, nor __INLINE_CODE_156__ option is defined, the __INLINE_CODE_157__ option defaults to __INLINE_CODE_158__ function, so its either forwarding __INLINE_CODE_159__ if any or renders nothing.
If both __INLINE_CODE_160__ option is provided and component-to-wrap is passed, then __INLINE_CODE_161__ takes the precedence.
Note: Why not use the __INLINE_CODE_162__ option in place of wrapped component all the time? Well, its technically possible, espeically if your React component is a stateless functional component anyway.
But regular HOC wrapping is just common technique in React world and is nicely composable when you have other HOCs than __INLINE_CODE_163__.
And is cleaner too - you might be prototyping your app as a set of __INLINE_CODE_164__ components first, without even involving __INLINE_CODE_165__, then "connect" them all at once, or introduce __INLINE_CODE_166__ incrementally. Whatever :)
__INLINE_CODE_167__ option
Your component's __INLINE_CODE_168__ is by default automatically assigned to the following (depending on whether the wrapped component is provided, or __INLINE_CODE_169__ option, or neither:
- __INLINE_CODE_170__ when WrappedComponent is passed
- __INLINE_CODE_171__ when __INLINE_CODE_172__ prop is defined (and is truthy)
- __INLINE_CODE_173__ otherwise
You can override that like with any regular React component:
const ConnectedComponent = cycleConnect({
...
)(Component);
ConnectedComponent.displayName = 'ConnectedComponent';
But there's also a __INLINE_CODE_174__ option to set the generated (connected) component __INLINE_CODE_175__, which looks a bit cleaner:
const ConnectedComponent = cycleConnect({
...
displayName: 'ConnectedComponent'
)(Component);
Inside the Cycle
__INLINE_CODE_176__ really assumes that you know how to use Cycle.js. Its really a very simple yet very elegant pattern, so make sure to familiarize yourself with it.
Quick recap on what Cycle.js __INLINE_CODE_177__ function is. Its basically a pure function with the signature:
main :: (Sources) => Sinks
It accepts __INLINE_CODE_178__ as an argument and returns __INLINE_CODE_179__ and describes a dataflow using composition of reactive streams and their transformations with pure functions.
Please note that the __INLINE_CODE_180__ function is only run once per __INLINE_CODE_181__ component lifecycle. It doesn't run again on rerendering or something. This is because the actual call to __INLINE_CODE_182__ is to just build up a dataflow, not to get some rendering data.
The data flows through reactive streams, from __INLINE_CODE_183__ to __INLINE_CODE_184__ of the __INLINE_CODE_185__, until its __INLINE_CODE_186__ are unsubscribed from parent Cycle program's __INLINE_CODE_187__ (this happens when React component is unmounted).
Drivers
The __INLINE_CODE_188__ is supposed to run side effects for a Cycle.js program. Its outside the program itself, supposed to be impure and connects your Cycle app with the "outer world". This is an essential concept and is described in a great detail in Cycle.js docs on drivers.
There are no differences from idiomatic Cycle.js in how __INLINE_CODE_189__ handles __INLINE_CODE_190__ - it just hands it over to __INLINE_CODE_191__ directly. There is only one __INLINE_CODE_192__ driver (an __INLINE_CODE_193__ driver) that is always added internally by the __INLINE_CODE_194__ed component - it allows to invoke functions outside the __INLINE_CODE_195__ that is to be maintained pure.
Wrappers
The __INLINE_CODE_196__ is a rather simple thing - its just a function that takes __INLINE_CODE_197__ function that wraps it with the other __INLINE_CODE_198__ function that might in turn add some logic in between:
function someWrapper(originalMain) {
return wrappedMain(sources) {
// Can do something here, like transforming `sources` somehow
// ...
// Invoke the original `main` internally to obtain its sinks
const sinks = originalMain(sources);
// Can do something to `originalMain`s sinks here
// ...
return {
...sinks,
someOtherSink: xs.of({ ... }) // Can add a new sink
};
}
}
It is important that the wrapping __INLINE_CODE_199__ is maintained pure, so that your entire program is pure. If you need some effectful logic to run in a wrapping __INLINE_CODE_200__, you should consider using driver for that. Wrappers, just like any regular __INLINE_CODE_201__ function, can internally use __INLINE_CODE_202__ that are created from __INLINE_CODE_203__.
Wrappers can also add arbitrary __INLINE_CODE_204__ and could expect certain __INLINE_CODE_205__ to be returned from the Cycle program, depending on particular wrapper implementation.
Wrappers are usually created to encapsulate some non-business-logic stuff, you'd be normally describe anything related to the app itself in the "original" __INLINE_CODE_206__ function.
Most of __INLINE_CODE_207__ (aside from __INLINE_CODE_208__ HOC itself) is implemented with wrappers, that are created for each __INLINE_CODE_209__ed component automatically:
- __INLINE_CODE_210__ (to mess around with React component props)
- __INLINE_CODE_211__ (a gateway to React component lifecycle streams)
- __INLINE_CODE_212__ (to route the view events back from your wrapped React component aka view, and pass the events upstream, to parent components)
Sources
This is a Cycle.js program's input. __INLINE_CODE_213__ function shouldn't care whether something on the __INLINE_CODE_214__ is a __INLINE_CODE_215__ or some custom thing provided by one of the wrappers.
For __INLINE_CODE_216__ connected component, the __INLINE_CODE_217__ are __INLINE_CODE_218__ directly plus whatever __INLINE_CODE_219__ add to them.
Sinks
This is a Cycle.js program's output.
__INLINE_CODE_220__ invokes your __INLINE_CODE_221__ed component's __INLINE_CODE_222__ function, takes its __INLINE_CODE_223__ and subscribes the __INLINE_CODE_224__ passed from the parent component to these __INLINE_CODE_225__. Its how __INLINE_CODE_226__ is "attaching" the new component to a tree of Cycle programs.
If there's no parent component (thus, no __INLINE_CODE_227__) and your component is a "Root Cycle", then the sinks are handled to __INLINE_CODE_228__ to "run" as with a regular Cycle.js app.
React __INLINE_CODE_229__
The __INLINE_CODE_230__ed __INLINE_CODE_231__ component is essentially a view for the associated Cycle.js __INLINE_CODE_232__ function. And __INLINE_CODE_233__ encourages the view to be a "dumb" stateless function that takes __INLINE_CODE_234__ as an argument ad returns something of __INLINE_CODE_235__ type (i.e., __INLINE_CODE_236__s, __INLINE_CODE_237__, etc, whatever __INLINE_CODE_238__ component renders).
Note: The wrapped __INLINE_CODE_239__ shouldn't necessarily be a stateless functional component (SFC), you could have any __INLINE_CODE_240__ component wrapped, but its advisable to use functions for the most part as the whole point of __INLINE_CODE_241__ is to use __INLINE_CODE_242__ for anything that is not a view.
So, the __INLINE_CODE_243__ is the only communication channel from our Cycle.js part (the connected component's __INLINE_CODE_244__ function) to connected __INLINE_CODE_245__ component itself. We therefore need to pass the props from our Cycle program to the view (a __INLINE_CODE_246__ component) somehow.
This is where __INLINE_CODE_247__ comes into play and provides __INLINE_CODE_248__ source and sink for a __INLINE_CODE_249__ function. Its local to a component, so no other components will share the same __INLINE_CODE_250__ source or sink.
__INLINE_CODE_251__ sink
The __INLINE_CODE_252__ sink on __INLINE_CODE_253__ed is there to pass the props to the view:
function main(sources) {
return {
props: xs.of({ greetable: 'World' })
};
}
// Use the passed `greetable` prop
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
Remember, that the value you map to __INLINE_CODE_254__ sink should be an object of new props and that updated props will be merged with existing component __INLINE_CODE_255__ like:
const updatedProps = Object.assign({}, prevProps, newProps);
This means that you can easily overwrite any props you want and leave the others intact.
Also, note above that we're passing the __INLINE_CODE_256__ through an __INLINE_CODE_257__ stream and that might be synchronous (like in our example with a stream of static data that is sent immediately) but could also be asynchronous. In that case the value of some prop might be not yet available on the first render:
function main(sources) {
return {
props: xs.of({ greetable: 'World' }).compose(delay(3000))
};
}
// Trying to use the passed `greetable` prop
// But whooops, no `greetable` yet :(
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
So either provide a default value for that async prop's __INLINE_CODE_258__ or perform additional checks in the view, when rendering.
Note: When messing with the __INLINE_CODE_259__ in your __INLINE_CODE_260__, remember that any props that are passed from outside as an input to a __INLINE_CODE_261__ component, are also there (including callback functions, if there are any) and are also merged upon updates from outside (on __INLINE_CODE_262__).
Lets summarize: the __INLINE_CODE_263__ from the __INLINE_CODE_264__ sink are passed from the __INLINE_CODE_265__ function to the view (__INLINE_CODE_266__ component).
__INLINE_CODE_267__ source
Not only you'll need to provide the props to the view for rendering, but oftentimes you'd need your __INLINE_CODE_268__ to take part in transformations/calculations in the __INLINE_CODE_269__ function itself. For that reason, __INLINE_CODE_270__ provides the __INLINE_CODE_271__ source as well.
It allows you to have an __INLINE_CODE_272__ stream of your component's input __INLINE_CODE_273__. Note that the __INLINE_CODE_274__ is of type __INLINE_CODE_275__ and to take the actual stream of props, use its __INLINE_CODE_276__ attribute:
function main(sources) {
const name$ = sources.props.props$.map(props => props.name);
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
function MyComponent(props) {
return (
<div>
Hello, {props.name}! Your father is Mr. {props.father}.
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
...
// Somewhere else:
<MyConnectedComponent name="Luke Skywalker" father="Darth Vader" />
// renders:
//
// <div>
// Hello, LUKE SKYWALKER! Your father is Mr. Darth Vader.
// </div>
The new value on __INLINE_CODE_277__ is emitted every time __INLINE_CODE_278__ change. This could be overkill for the most cases, as you would only need just few props for your dataflow.
This is what __INLINE_CODE_279__ method for, it accepts the name of the nested prop (will return __INLINE_CODE_280__ if the stream value is not an object) and returns a stream of that prop only that is only got new value once this particular prop changes:
function main(sources) {
const name$ = sources.props.pluck('name');
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
The __INLINE_CODE_281__ed stream also performs shallow comparison and does __INLINE_CODE_282__ behind the scenes, so identical values don't cause a new value emitted on an __INLINE_CODE_283__.
Note that you don't need to always pull all props from your __INLINE_CODE_284__ function's __INLINE_CODE_285__ just for the sake of it to just forward to the __INLINE_CODE_286__ sink directly, you better only pick those you need in a dataflow with __INLINE_CODE_287__. The __INLINE_CODE_288__ passed from outside will be available on the view normally. You can overwrite them from inside your __INLINE_CODE_289__ though.
There's also __INLINE_CODE_290__ method that also accepts a nested prop name. The difference is that the result is not an __INLINE_CODE_291__ stream of prop values but the __INLINE_CODE_292__ instance itself that is scoped to that piece of __INLINE_CODE_293__:
function main(sources) {
const name$ = sources.props.pluck('name'); // Stream of strings
const nameSource = sources.props.select('someNestedObject'); // Scoped ReactPropsSource
const someNestedObjProperty$ = nameSource.pluck('someNestedObjectProperty');
...
}
This is useful when you want to __INLINE_CODE_294__ a stream of some sub-prop from deeper nested objects.
Note: Taking deep property stream this way isn't really nice currently as requires quite a bit of boilerplate - this part of API is already being thought out further and reconsidered.
Interactions
So far so good, but rendering the __INLINE_CODE_295__ isn't the only exciting thing we want to do with __INLINE_CODE_296__, right? We also want to handle events coming from the view(e.g., the DOM).
The way __INLINE_CODE_297__ handles events and nested components' callbacks is the biggest deviation from an idiomatic Cycle.js app using __INLINE_CODE_298__ driver. It refrains from a holistic "no handlers in the view" approach for a few reasons:
- We need something more in line with __INLINE_CODE_299__ as we're using React here
- We're not always handling DOM events but also need to handle some children components data/events passed back. We can't pass streams like in regular Cycle.js app, in __INLINE_CODE_300__ we do it via callbacks that are provided amongst children components' __INLINE_CODE_301__ and we need __INLINE_CODE_302__ed components to be compatible __INLINE_CODE_303__ components at all levels.
This is what "interactions" feature of __INLINE_CODE_304__ is for. It provides the unified interface for parent-child components communication and event handling that is aligned with a regular event/callback handling model on __INLINE_CODE_305__ side but also allows to access those events in form of __INLINE_CODE_306__ streams inside the __INLINE_CODE_307__ function.
Events triggering by the view
Every __INLINE_CODE_308__ component has some additional __INLINE_CODE_309__-specific props injected, so they are available on your view.
These are __INLINE_CODE_310__ which is a function that takes the __INLINE_CODE_311__ (a string) and optional __INLINE_CODE_312__ (of any type) and returns a function that will be a callback for your event or some child component prop function.
So, the signature is:
(interactionType, predefinedValue?) => (value) => void
Its supposed to be called imperatively in your view and is available as __INLINE_CODE_313__:
function MyComponent({ interact }) {
return (
<div>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
For the sake of being cleaner (and for better TypeScript support), __INLINE_CODE_314__ also leverages the ES2015 __INLINE_CODE_315__ feature to provide the object with dynamic properties, each representing an __INLINE_CODE_316__ with __INLINE_CODE_317__ dynamically defined after the function property name. Its available on __INLINE_CODE_318__ so we can rewrite the above snippet as:
function MyComponent({ interactions }) {
return (
<div>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
In the example above, the __INLINE_CODE_319__ function receives the click __INLINE_CODE_320__ as an argument and that is what you'll get in your __INLINE_CODE_321__ (next section).
While cleaner, it doesn't allow for the __INLINE_CODE_322__ to be set, so if you need some particular value to be emitted by the associated stream in your __INLINE_CODE_323__, you can either use __INLINE_CODE_324__ or create an inline function:
// Assuming you have `id` available, e.g.
// when looping/mapping over the collection of items
<button
type="button"
className="delete-btn"
onClick={interact('delete', id)}
>
Click me!
</button>
// OR
<button
type="button"
className="delete-btn"
onClick={() => interactions.delete(id)}
>
Click me!
</button>
View events handling with __INLINE_CODE_325__ source
There's internal __INLINE_CODE_326__ in __INLINE_CODE_327__ and it provides the __INLINE_CODE_328__ source for our __INLINE_CODE_329__ function. The __INLINE_CODE_330__ source is of type __INLINE_CODE_331__, which has the __INLINE_CODE_332__ method, which we can use to obtain interaction streams:
function main(sources) {
const buttonClick$ = sources.interactions.select('buttonClick');
// Do something with a buttonClick$, e.g.
// map it to the click coordinates props
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interact, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
The streams are __INLINE_CODE_333__ed by the same __INLINE_CODE_334__ string you provided to the __INLINE_CODE_335__ function on your view.
Similar to how __INLINE_CODE_336__ object with dynamic function-props is available on your view, __INLINE_CODE_337__ is a ES2015 __INLINE_CODE_338__ and it maintains the dynamic streams right on itself (just don't use __INLINE_CODE_339__ as its reserved method name here here):
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interactions, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
Note: If you try to get the non-existing stream, it will still work, this thing is dynamic, so the stream will be created nevertheless but will just never emit values. Be careful and look out for typos! Using TypeScript is highly recommended to avoid typos here.
Upstream interactions with __INLINE_CODE_340__ sink (calling callbacks on __INLINE_CODE_341__).
Now that we can handle the streams of view events and values from the children components, we would also need to somehow call the callbacks that are passed among our __INLINE_CODE_342__.
First, nothing prevents from following the common React way and call those directly from the view:
function MyComponent({ onButtonClick }) {
return (
<button type="button" onClick={onButtonClick}>
Click me!
</button>
);
}
// Somewhere else:
doSomething = (event) => { console.log('clicked!'); };
...
<MyComponent onButtonClick={doSomething} />
This is fine if you don't need that value's stream in your dataflow. This approach bypasses your Cycle.js program's __INLINE_CODE_343__ completely and doesn't make it impure by otherwise possibly passing an effectful function as __INLINE_CODE_344__. There's even no need to __INLINE_CODE_345__ your component in the above example.
Keep in mind that if __INLINE_CODE_346__ is optional there, you will also need to check whether that is defined before providing that as a callback (use __INLINE_CODE_347__ or TypeScript to mark it as either required or optional).
Oftentimes you will need to call some callback that lives on the __INLINE_CODE_348__ though, and this is what __INLINE_CODE_349__ sink provided by __INLINE_CODE_350__ does:
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
const coords$ = buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
);
// We both update the `props` (to render it in this component)
// and pass upstream
return {
props: coords$,
interactions: xs.of({
onCoordsChange: coords$
})
};
}
function MyComponent({ interactions }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
handleCoordsChange = (coords) => {
console.log('New coordinates received: ', coords);
};
...
<ConnectedComponent onCoordsChange={handleCoordsChange} />
// The new coords will be rendered by the component
// and the console will show the "New coordinates received"
// with new coords too.
Once the appropriate stream emits a value, that callback is called. It will also check if the callback was passed in the first place to skip the call attempt if no callback with a given name is passed among the __INLINE_CODE_351__.
Important: Note that __INLINE_CODE_352__ sink expects a stream of object whose property names correspond to callback names on __INLINE_CODE_353__ and property values are themselves streams too - whatever values those streams emit, will be passed as an argument to the callback.
So far so good, but we know that our __INLINE_CODE_354__ is a wrapper and not a driver, so what if one passed the effectful function as a callback, e.g., __INLINE_CODE_355__ passed as __INLINE_CODE_356__ above does a __INLINE_CODE_357__ internally - that is a side effect, right?
Well, this is why the function is not invoked directly, or inside the wrapper (since the wrapper is the part of the pure land of our __INLINE_CODE_358__s), its using the __INLINE_CODE_359__ driver internally. More on this below in a section about Side-effect control, just keep in mind that __INLINE_CODE_360__ and __INLINE_CODE_361__ should be pure and __INLINE_CODE_362__ follows that requirement.
React component lifecycle streams
You're probably not going to need __INLINE_CODE_363__ component lifecycle hooks often inside your __INLINE_CODE_364__, but still - they are provided via special __INLINE_CODE_365__. It provides the __INLINE_CODE_366__ source that is available on each __INLINE_CODE_367__ed __INLINE_CODE_368__ sources.
The lifecycle events are provided as __INLINE_CODE_369__ streams and their names mostly mirror the React lifecycle methods, but shortened to remove some redundant __INLINE_CODE_370__ prefixes:
React lifecycle method
__INLINE_CODE_371__ source property
Stream value
__INLINE_CODE_372__
__INLINE_CODE_373__
__INLINE_CODE_374__
__INLINE_CODE_375__
__INLINE_CODE_376__
__INLINE_CODE_377__
__INLINE_CODE_378__
__INLINE_CODE_379__
__INLINE_CODE_380__
__INLINE_CODE_381__
__INLINE_CODE_382__
__INLINE_CODE_383__
__INLINE_CODE_384__
__INLINE_CODE_385__
__INLINE_CODE_386__
__INLINE_CODE_387__
__INLINE_CODE_388__
__INLINE_CODE_389__
__INLINE_CODE_390__
__INLINE_CODE_391__
Error data
The "error data" on the __INLINE_CODE_392__ is an object of this shape:
{
error: Error;
errorInfo: ErrorInfo;
}
These are basically React __INLINE_CODE_393__ arguments squashed into a single object.
Also, note that __INLINE_CODE_394__ hook is not present there, since that is not really an event and affects how your component is rendered. __INLINE_CODE_395__ would be providing its own custom internal __INLINE_CODE_396__ instead for optimisation purposes and you could implement it on your wrapped component anyway.
Note: As well, no lifecycle events are passing anything related to React component __INLINE_CODE_397__, only taking __INLINE_CODE_398__ into account instead. This is because the __INLINE_CODE_399__ is meant to be managed differently when Cycle.js is in use. You can still use that for wrapped component but its out of play on the component generated by __INLINE_CODE_400__.
A quick example:
function main(sources) {
const willUnmount$ = { sources.lifecycle };
// Do something on unmounting, e.g. report that to a console
// via some hypothetical `logger` driver
return {
logger: willUnmount$.mapTo(`I'll be unmounted soon!`)
};
}
Side-effect control and __INLINE_CODE_401__ driver
Side-effect control is where Cycle.js really shines (not only implementation, it applies to the "cycle pattern" behind Cycle.js in the first place).
In order to keep the __INLINE_CODE_402__ part of __INLINE_CODE_403__ed component pure, we need to make sure that any potentially effectful function is not run inside our __INLINE_CODE_404__ directly.
This is what __INLINE_CODE_405__ driver is responsible for. It allows to only describe what function you want to call and with what arguments, then delegate that to a __INLINE_CODE_406__ which is supposed to run any impure functions outside of the app itself.
The way it works is very similar to that of __INLINE_CODE_407__ driver from __INLINE_CODE_408__ package where you don't actually run the request inside your __INLINE_CODE_409__ but just describe it and pass to __INLINE_CODE_410__ driver to handle. __INLINE_CODE_411__ driver is just a bit more generic and operates on functions.
__INLINE_CODE_412__ uses __INLINE_CODE_413__ driver internally (to run effects for upstream __INLINE_CODE_414__), but it is also available for you on any connected __INLINE_CODE_415__ as __INLINE_CODE_416__ sink automatically (i.e., without explicit configuration on a root cycle):
function log() {
console.log(`I'm effectful!`);
}
function saveToLocalStorage(message) {
localStorage.setItem('message', message);
}
function main(sources) {
const someCallbackFn$ = sources.props.pluck('someCallbackFunction');
return {
fnCallEffect: xs.merge(
xs.of({ fn: log }),
xs.of({ fn: saveToLocalStorage, args: ['some message to save'] }),
// Call the `props` callback function directly, this is how
// `interactions` sink works behind the scenes:
someCallbackFn$.map(fn => ({ fn }))
)
};
}
Isolation
Isolation is Cycle.js way of making the component's __INLINE_CODE_417__ and __INLINE_CODE_418__ isolated from influence of other components.
Make sure to carefully read more about Cycle.js isolation here. It might take a bit to get used to an idea.
To reiterate: the concept of isolation is important for Cycle.js apps. Its a little bit different for __INLINE_CODE_419__ because most things are scoped to a particular __INLINE_CODE_420__ed component already, like components have "local" __INLINE_CODE_421__, __INLINE_CODE_422__ and __INLINE_CODE_423__ sources and sinks and __INLINE_CODE_424__ components are also self-contained already.
There are though __INLINE_CODE_425__/__INLINE_CODE_426__ that will need the isolation feature, like __INLINE_CODE_427__ driver or __INLINE_CODE_428__ wrapper (a state management solution for Cycle.js apps). So, isolation makes sense for shared __INLINE_CODE_429__/__INLINE_CODE_430__, those you pass to __INLINE_CODE_431__ function (by defining the __INLINE_CODE_432__/__INLINE_CODE_433__ options on root __INLINE_CODE_434__) and that will be available across the entire tree of Cycle programs.
Note: The isolation works by scoping the source streams to particular component and a kind of unscoping of the sink streams before connecting to parent's sinks.
Also note, that each driver/wrapper decides on its own how its sources/sinks should be isolated.
In Cycle.js, you use the __INLINE_CODE_435__ function from __INLINE_CODE_436__ package directly, as you're just invoking the children __INLINE_CODE_437__ functions from your current component's __INLINE_CODE_438__ function:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// Do something with childSinks, combine somehow, etc
return {
...
};
}
__INLINE_CODE_439__ function has this simple signature:
isolate :: (MainFn, scope) => MainFn
It takes the __INLINE_CODE_440__ function and an optional __INLINE_CODE_441__ and returns an isolated __INLINE_CODE_442__ function.
__INLINE_CODE_443__ does also use the __INLINE_CODE_444__ package internally, but you can't call __INLINE_CODE_445__ directly on some __INLINE_CODE_446__ function. You'd never call one component's __INLINE_CODE_447__ from the other component's __INLINE_CODE_448__ directly with __INLINE_CODE_449__.
Naive explicit isolation (conceptually incorrect)
Just to illustrate the issue, there's an attempt to isolate the component's __INLINE_CODE_450__ sources, so that HTTP requests initiated from that components are scoped to it (and are unavailable to siblings).
Since we know that __INLINE_CODE_451__ returns a __INLINE_CODE_452__ function and __INLINE_CODE_453__ takes the __INLINE_CODE_454__ function as an argument, lets try to feed it an isolated version of it:
function main(sources) {
...
}
function MyComponent() {
...
}
const isolatedMain = isolate(main, { HTTP: 'myComponent' });
const ConnectedComponent = cycleConnect(isolatedMain)(MyComponent);
While this will won't raise any errors (because this is valid code after all), this approach brings a subtle issue.
This will isolate things for this particular components but not their children components, so in this case, children (connected childred __INLINE_CODE_455__ components) will have no __INLINE_CODE_456__ isolation scope for __INLINE_CODE_457__ channel.
This might seems like a no-issue, because hey, you can define different scopes for the children components right? Well, while this is technically possible, its conceptually incorrect and it will be completely broken in some cases, e.g., with __INLINE_CODE_458__ where the deeper you go - the "more filtered" source streams you get.
Important! The above note about it being "conceptually incorrect" refers to the way __INLINE_CODE_459__ maintains the tree of Cycle.js programs internally. It tries to make the entire app structure look the same way as you'd manually write a Cycle.js app. So, when you __INLINE_CODE_460__ at some level, then if inside your __INLINE_CODE_461__ you also call some __INLINE_CODE_462__ happens, then __INLINE_CODE_463__ gets isolated sources no matter what.
__INLINE_CODE_464__ option for __INLINE_CODE_465__
The first way to correctly isolate __INLINE_CODE_466__ed component is to use the __INLINE_CODE_467__ option for __INLINE_CODE_468__ itself, so the above can be rewritten as:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main, {
isolate: { HTTP: 'myComponent' }
})(MyComponent);
This will work perfectly, with all children __INLINE_CODE_469__ed components having __INLINE_CODE_470__ namespace applied to __INLINE_CODE_471__ sources/sinks in their __INLINE_CODE_472__ functions.
The __INLINE_CODE_473__ option accepts a __INLINE_CODE_474__ or the __INLINE_CODE_475__ and in that case its passed directly to __INLINE_CODE_476__ package's __INLINE_CODE_477__ as a __INLINE_CODE_478__ argument. So, check out the __INLINE_CODE_479__ function API reference.
__INLINE_CODE_480__ also makes it accept a function that takes component __INLINE_CODE_481__ as an argument and should return the scope of __INLINE_CODE_482__ or __INLINE_CODE_483__ type that is then also passed to __INLINE_CODE_484__ package's __INLINE_CODE_485__. This allows for some dynamic isolation scopes to be set based on the React component's initial props.
Important: Despite the __INLINE_CODE_486__ is provided as an option to __INLINE_CODE_487__ when defining the connected component, the actual isolation will only happen when component instance is created. This is why its possible to dynamically resolve the __INLINE_CODE_488__ based on the component __INLINE_CODE_489__ in __INLINE_CODE_490__.
__INLINE_CODE_491__ prop
Despite the __INLINE_CODE_492__ option will work nicely for some cases, there's a pattern in how it is utilized in pure Cycle.js programs. Lets take a look at pure Cycle.js example again:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// ...
return {
...
};
}
The important part is that in Cycle.js app its not a child who decides how to isolate itself but __INLINE_CODE_493__ is provided from the parent instead.
Most of the time, we want to maintain the same approach with __INLINE_CODE_494__. The advantage is that component in this case is totally agnostic from the provided scope, it doesn't even care whether its isolated or not at all - all it knows is that it expects certain __INLINE_CODE_495__ and returns certain __INLINE_CODE_496__. This makes the component reusable.
This is especially important with __INLINE_CODE_497__, where you want to control where your child components "attach" to the state.
In other words, the parent decides how to compose children components, not the other way around, because otherwise the child would need to have an intimate knowledge of something that really belongs to parent and thus not being reusable and breaking encapsulation.
For this reason, every __INLINE_CODE_498__ed React component accepts an __INLINE_CODE_499__ prop, so you can use that just like any other prop:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
// Isolate everything
<ConnectedComponent isolate="myComponent" />
// Isolate HTTP only
<ConnectedComponent isolate={{ HTTP: 'myComponent' }} />
<ConnectedComponent isolate={{ HTTP: 'myComponent_2' }} />
In contrast with __INLINE_CODE_500__ option, the __INLINE_CODE_501__ prop can only accept __INLINE_CODE_502__ or __INLINE_CODE_503__ that is directly passed to __INLINE_CODE_504__ function from __INLINE_CODE_505__, thus maintaining full parity with pure Cycle.js approach. Indeed, having a function that accepts initial __INLINE_CODE_506__ wouldn't make much sense because you're anyway providing props that could be different for different component instances when rendering that component.
__INLINE_CODE_507__ helper component
Since isolation in __INLINE_CODE_508__ applies to both "current" __INLINE_CODE_509__ed component and all its __INLINE_CODE_510__ed children, we might sometimes need to define some connected component to only be a common parent for some children, which must provide isolation but wouldn't contain any logic.
This is what a helper __INLINE_CODE_511__ component for. It accepts the __INLINE_CODE_512__ prop (that is directly passed as a __INLINE_CODE_513__ argument to __INLINE_CODE_514__ function of __INLINE_CODE_515__):
<Isolator scope={{ HTTP: 'someNamespace' }}>
<ConnectedComponent someProps="1" />
<ConnectedComponent someProps="2" />
<ConnectedComponent someProps="3" />
</Isolator>
This assists the isolation composition at view level.
Extra: State management with __INLINE_CODE_516__
__INLINE_CODE_517__ is the idiomatic Cycle.js fractal state management solution. It is tailored for Cycle.js apps but since __INLINE_CODE_518__ is using the real Cycle.js, it stays fully compatible here as well.
Using __INLINE_CODE_519__ with __INLINE_CODE_520__ is optional, since you might be using different lib (e.g., __INLINE_CODE_521__) for that.
To start, just read the onionify docs and install the package:
$ npm install --save cycle-onionify
and pass that with __INLINE_CODE_522__ array to a __INLINE_CODE_523__ connected component (e.g., Root Cycle):
// RootCycle.js
import { run } from '@cycle/run';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
wrappers
})();
export default RootCycle;
Now every __INLINE_CODE_524__ed component that is a child of the above-mentioned __INLINE_CODE_525__ in React component tree got the __INLINE_CODE_526__ source and sink.
Important: Notice that you're all set - its purely a Cycle.js land. __INLINE_CODE_527__ doesn't provide any additional functionality for __INLINE_CODE_528__. Instead, it provides few helpers for isolation and working with collections on the React side.
All the exportables related to __INLINE_CODE_529__ reside in the extra package: __INLINE_CODE_530__. It is installed along with the main lib but is not __INLINE_CODE_531__d/__INLINE_CODE_532__ed by default, so won't be bundled (by Webpack, Rollup, etc) unless you're using it.
This extra sub-package contains few things:
- __INLINE_CODE_533__ helper component
- __INLINE_CODE_534__ helper component
- __INLINE_CODE_535__ helper factory function
Read ahead for details on each.
In __INLINE_CODE_536__, the state/reducers layer composition is done via isolation, so the first thing here is __INLINE_CODE_537__:
__INLINE_CODE_538__ helper component
This is pretty much like __INLINE_CODE_539__ but is here to specifically isolate __INLINE_CODE_540__ sources/sinks only.
So, the:
import { StateIsolator } from 'react-cycle-connect/lib/extra/onionify';
// ...
<StateIsolator lens="someStateProperty">
<NestedComponent />
</StateIsolator>
is somewhat a convenient shortcut to:
import { Isolator } from 'react-cycle-connect';
// ...
<Isolator scope={{ onion: 'someStateProperty', '*': null }}>
<NestedComponent />
</Isolator>
Notice how __INLINE_CODE_541__'s prop is explicitly named __INLINE_CODE_542__. It just implies that oftentimes you'll be using "lens" to isolate the state. Consider a string just a shortcut to a more elaborate __INLINE_CODE_543__ object.
In the example above, the __INLINE_CODE_544__'s __INLINE_CODE_545__ function __INLINE_CODE_546__ will represent only that "someStateProperty" piece of state. Pretty much how you'd __INLINE_CODE_547__ the __INLINE_CODE_548__ channel in an idiomatic Cycle.js app.
There's also a __INLINE_CODE_549__ property, so that you can change the name of the source/sink inside your __INLINE_CODE_550__ to something else. Thats to be on par with __INLINE_CODE_551__ features, where you can also change that name.
Handling collection state manually
Managing collections of components in pure Cycle.js with __INLINE_CODE_552__ is a kind of a recognized challenge. This is why __INLINE_CODE_553__ ended up providing __INLINE_CODE_554__ helper function - see their docs for more detail.
While __INLINE_CODE_555__ also has a __INLINE_CODE_556__ helper component that is very similar to __INLINE_CODE_557__ __INLINE_CODE_558__ function, lets first try to pretend we don't have it and imagine how'd we handle the state without it.
Note: Nothing prevents you from skipping right to a description of how __INLINE_CODE_559__ works, but it might be useful to understand the rationale behind it, so reading this section is recommended.
Handling collections is different with React than in Cycle.js, e.g., you can always just render React components in a loop or __INLINE_CODE_560__ an array of data to an array of components and React will render that.
When you want to render some __INLINE_CODE_561__ data from your state, its easy without any helpers:
:
{
list: [
{ id: 1, name: 'Obi-Wan Kenobi', kills: 100 },
{ id: 2, name: 'Luke Skywalker', kills: 25 },
{ id: 3, name: 'Darth Vader', kills: 9000 }
]
}
*/
// ListItem.jsx
function ListItem({ itemData }) {
return (
<li>
{itemData.name} (ID: {itemData.id}, kills: {itemData.kills})
</li>
);
}
// List.jsx
function main(sources) {
// Just pass as `props` directly
return {
props: sources.onion.state$
};
}
function List({ list }) {
return (
<ul>
{list.map(item => (
<ListItem key={item.id} itemData={item} />
))}
</ul>
);
}
const ConnectedList = cycleConnect(main)(List);
This keeps your __INLINE_CODE_562__ component just a "dumb" view, there's no even need of connecting it to the Cycle tree. So far so good.
Then you want to add more item-level capabilities, like editing an item (incrementing hero __INLINE_CODE_563__ in our case, for simplicity) or deleting an item. You can still manage the state in the __INLINE_CODE_564__ component, act upon some callbacks for particular item and only stream reducers from inside __INLINE_CODE_565__ component's __INLINE_CODE_566__:
/*
Assume you have this value on your `List` component `stateFork
- updated to more recent version of cycle, including @cycle/state
- updated to React 16.9 api, including new lifecycles and context api
- merged onionify separate lib so import is __INLINE_CODE_0__
- added tests
- published at @iilab/react-cycle-connect
react-cycle-connect
An experimental library that allows using Cycle.js as a reactive dataflow framework for React.
Written in TypeScript.
This little thing allows to define your app's view layer using regular React components, but implement the actual logic behind it using pure functional reactive approach, taking advantage of Cycle.js' Model-View-Intent pattern and its exceptional side effect control.
This isn't a custom implementation of Cycle pattern for __INLINE_CODE_1__ but a link to real Cycle.js instead, so any Cycle.js drivers and wrappers can be applied (e.g., __INLINE_CODE_2__ for state management). Also, for this reason, some knowledge about how __INLINE_CODE_3__ works is assumed.
Note: This is an experimental lib and currently works with __INLINE_CODE_4__ and __INLINE_CODE_5__ only. If all goes good, the support for __INLINE_CODE_6__/__INLINE_CODE_7__ (instead of __INLINE_CODE_8__) and for __INLINE_CODE_9__/__INLINE_CODE_10__ (instead of __INLINE_CODE_11__) is anticipated. Nothing prevents from having __INLINE_CODE_12__ covered too.
UPDATE (MAR 2018): Some internals overhaul is pending and it's currently waiting for Cycle.js to settle on hybrid push-pull reactivity and have internals rewritten to use Callbags instead of __INLINE_CODE_13__. So, __INLINE_CODE_14__ is going to be using Callbags by default too.
Any feedback is highly appreciated!
Table of Contents
- react-cycle-connect
- Table of Contents
- Shup up and show me the code
- Installation
- Motivation
- How it works (architecture)
- Guide
- API reference
- Alternatives / Competitors
- FAQ
- Roadmap
Shup up and show me the code
Cycle.js's counter example ported over:
import React from 'react';
import ReactDOM from 'react-dom';
import xs from 'xstream';
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
// Your Cycle.js `main` function
function main(sources) {
const action$ = xs.merge(
sources.interactions.decrement.map(ev => -1),
sources.interactions.increment.map(ev => +1)
);
const count$ = action$.fold((acc, x) => acc + x, 0);
return {
props: count$.map(count => ({ count }))
};
}
// Your React component
function App({ interactions, count }) {
return (
<div>
<button onClick={interactions.decrement}>Decrement</button>
<button onClick={interactions.increment}>Increment</button>
<p>Counter: {count}</p>
</div>
);
}
// Some connection magic
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
// Render normally as it's a regular React app
ReactDOM.render(
<ConnectedApp />,
document.getElementById('root')
);
Also - there's official TodoMVC example with __INLINE_CODE_33__ for state management.
Installation
Using npm:
$ npm install --save react-cycle-connect
Using yarn:
$ yarn add react-cycle-connect
Motivation
The motivation to create this lib was based off the need to retain the React-style composability of the view layer using regular React components, reuse existing React components, make it (the lib) easily/incrementally adoptable in an ongoing React project, but also take advantage of the Cycle.js' MVI pattern and side effect control.
At the same time, while the most valuable thing from __INLINE_CODE_34__ is the pattern itself, there are numerous available libs/plugins for Cycle.js to take advantage of, letting us take the best of both worlds.
This way, you can still use anything from React stack (e.g., __INLINE_CODE_35__ to compose your routed app screens), but describe the interactions as a dataflow, with pure functions and reactive streams. Most of React components are going to be "dumb" (and also stateless functional aka SFC) since all the backing logic and cross-component communication would be handled by a tree of __INLINE_CODE_36__ programs.
Why not just use Cycle.js alone?
That is what I'd recommend considering in the first place. Its fast, its cool, the __INLINE_CODE_37__ (virtual DOM rendering engine behind Cycle's DOM driver) is goddamn fast and reliable. If you don't intend to use __INLINE_CODE_38__ components in any way, don't even get bothered. The __INLINE_CODE_39__ is more about "introducing Cycle.js to __INLINE_CODE_40__", not the other way around.
Is it a "Cycle.js in React" or "React in Cycle.js"?
More of a "Cycle.js in React". The app is bootstrapped by rendering a React component to the DOM, view is composed the React way. The root Cycle.js program is being __INLINE_CODE_41__ by rendering a root __INLINE_CODE_42__ed component. Though, it doesn't prescribe how you write your Cycle.js programs, and aside from the DOM generation part, its just Cycle.js all the way.
How it works (architecture)
Every __INLINE_CODE_43__ed component got a real Cycle.js program "attached" to it. The boundary between the two is __INLINE_CODE_44__. From your Cycle.js program standpoint, __INLINE_CODE_45__ is a view. Cycle.js program can update __INLINE_CODE_46__ that React component will then render.
A bunch of nested React components in your app represent a "tree of React components". __INLINE_CODE_47__ maintains a "tree of Cycle.js programs" that map to __INLINE_CODE_48__ed components and takes advantage of React's context feature to provide links between the nodes. Each connected component provides scoped links to the children nodes .
New "Cycle nodes" are attached lazily, upon associated React component initialization (this is where React controls how Cycle programs are run). The above-mentioned "link" is just an object on React __INLINE_CODE_49__ that brings couple properties:
- __INLINE_CODE_50__ for the child connected component's __INLINE_CODE_51__ function; and
- __INLINE_CODE_52__ to subscribe to that component's __INLINE_CODE_53__ function __INLINE_CODE_54__.
This is where something unusual for the Cycle.js happens - we subscribe to and unsubscribe from connected component's __INLINE_CODE_55__ function __INLINE_CODE_56__ based on React component's lifetime hooks. In Cycle.js, you normally just run the app at startup and let it run as an ongoing process. But this is where __INLINE_CODE_57__ performs the sync between the two worlds.
Overall, aside from using React __INLINE_CODE_58__ (which is just an implementation detail) and some intense wrapping on the __INLINE_CODE_59__ side to make laziness possible, __INLINE_CODE_60__ tries to represent the entire tree of Cycle.js programs as close as possible to what you'd end up with by writing idiomatic Cycle.js with explicit calling of nested component's __INLINE_CODE_61__ functions from the parent component __INLINE_CODE_62__ function. The result of children components is always __INLINE_CODE_63__d.
Aside from passing the __INLINE_CODE_64__ to children components, __INLINE_CODE_65__ also explicitly applies some internal wrappers to integrate with React component: __INLINE_CODE_66__, __INLINE_CODE_67__ and __INLINE_CODE_68__, that are scoped to a connected component's Cycle.js program only. It also explicitly runs the internal __INLINE_CODE_69__ driver for the __INLINE_CODE_70__ nodes by default - this is necessary for the __INLINE_CODE_71__ sink to work (because its supposed to run callbacks passed via __INLINE_CODE_72__ and that might be an effectful function).
Note that the entire internal "tree" of Cycle.js programs is maintained pure, including internal component-scoped wrappers, just as you would do with regular Cycle.js program.
Differences from idiomatic Cycle.js usage
Note: there's a difference in how'd you write the __INLINE_CODE_73__/__INLINE_CODE_74__ app vs pure __INLINE_CODE_75__ app. There's no misuse of Cycle.js per se, we just don't use it for rendering with __INLINE_CODE_76__ - and that applies certain implications to the approach.
__INLINE_CODE_77__ and __INLINE_CODE_78__, while both allow to create user interfaces, imply different approaches to the app architecture. The most important part here is that they are conceptually different when it comes to generating the VDOM.
In order to combine the two approaches nicely, there are certain deviations from how'd you write the __INLINE_CODE_79__-only app to allow for more optimal React DX.
The main difference from a pure __INLINE_CODE_80__ way is that its not a Cycle program that technically generates VDOM - instead, there's React component to represent a piece of UI. __INLINE_CODE_81__ approach to view/__INLINE_CODE_82__ event handling is rather purist - view only defines the markup and nothing else (i.e., user interaction/event handlers are defined inside Cycle program itself and not as some view component __INLINE_CODE_83__/etc attribute). That is not a crazy random opinion but instead a very well thought and weighted out decision by Cycle.js team. __INLINE_CODE_84__ takes a different approach, which isn't that strict and is more in line with React, with callbacks amongst __INLINE_CODE_85__ and all that stuff. It enables the events/component callbacks handling via interactions.
The view composition is obviously completely different. In __INLINE_CODE_86__ you run each component's __INLINE_CODE_87__ function that (possibly) generates a VDOM node and then explicitly combine the pieces of markup into a single VDOM stream that is then given to a DOM driver. With __INLINE_CODE_88__ you compose the view layer with React components normally. You can just connect those to Cycle.js program to "outsource" the actual logic to it.
Guide
This assumes the preliminary knowledge of both __INLINE_CODE_89__ and __INLINE_CODE_90__ (and __INLINE_CODE_91__). It doesn't provide much of the insight on how these work, but is instead focused on how __INLINE_CODE_92__ combines the two.
Basics
The result of __INLINE_CODE_93__ call is a HOC that optionally takes a React component (similar to that __INLINE_CODE_94__ function of __INLINE_CODE_95__) and returns a component that is augmented with backing Cycle.js program. This Cycle.js program is internally wrapped into a "Cycle Node" that, when initialized, is connected to a tree of Cycle.js programs. "Cycle Nodes" of every __INLINE_CODE_96__ed components are linked together via React __INLINE_CODE_97__.
Note, though, that there are no direct references from one component's Cycle program to another component's Cycle program, i.e., you're not invoking one __INLINE_CODE_98__ function from the other (this is perfectly possible though, but since we're not using __INLINE_CODE_99__ here, it would only handle non-DOM stuff). Instead, __INLINE_CODE_100__ handles the linking behind the scenes, so the "sibling" Cycle programs are fed with the same __INLINE_CODE_101__ and subscribe their __INLINE_CODE_102__ to the same __INLINE_CODE_103__.
There's essential concept of isolation in Cycle.js, so every __INLINE_CODE_104__ed component accepts an __INLINE_CODE_105__ prop, that is directly passed to __INLINE_CODE_106__ function from __INLINE_CODE_107__ package. Read more about Cycle.js isolation here.
Root Cycle
Every __INLINE_CODE_108__ed component provides a __INLINE_CODE_109__ object to its children through React __INLINE_CODE_110__ and also expects that __INLINE_CODE_111__ from some parent component (not necessarily immediate parent).
That link object has the following shape:
{
// Sources to provide to our Cycle.js program's `main` function as an argument
sources: { ... },
// sinkProxies to subscribe to our Cycle program's `main` function sinks
sinkProxies: { ... }
}
You shouldn't be working with that directly but its important that something needs to provide the top-level __INLINE_CODE_112__ and __INLINE_CODE_113__ in the first place. This is what __INLINE_CODE_114__ option of cycle connect for.
Also, the __INLINE_CODE_115__ed components' programs (__INLINE_CODE_116__ functions) are lazily attached to already running upper-level Cycle.js program, so the __INLINE_CODE_117__ program has to be __INLINE_CODE_118__.
So, root __INLINE_CODE_119__, aside from __INLINE_CODE_120__ option, also needs __INLINE_CODE_121__ defined, so that it knows how to run your top-level Cycle.js program. This would normally be just a __INLINE_CODE_122__ function from __INLINE_CODE_123__ package (or __INLINE_CODE_124__, or __INLINE_CODE_125__, depending on the reactive streams library in use).
You could just __INLINE_CODE_126__ your top-level __INLINE_CODE_127__ component like we did in the first example:
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
...
function main(sources) {
...
}
function App() {
return (
// Some JSX
...
);
}
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
Though this will work, its advisable to create a specialized __INLINE_CODE_128__ component just for this purpose:
// RootCycle.js
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
function main(sources) {
...
}
export const RootCycle = cycleConnect(main, {
root: true,
runFn: run
})(); // No component to wrap? Its okay, read ahead
export default RootCycle;
The top-level Cycle sources are actually __INLINE_CODE_129__ and __INLINE_CODE_130__, so root Cycle is also in charge of providing these __INLINE_CODE_131__ and __INLINE_CODE_132__ to any Cycle.js program down the tree, as __INLINE_CODE_133__.
__INLINE_CODE_134__ option is the object with keys defining properties of the __INLINE_CODE_135__ object, identical to how you'd do that in a regular Cycle.js program.
__INLINE_CODE_136__ option accepts an array of wrappers (order might matter there, so its a user-land option).
Also, the __INLINE_CODE_137__ function is optional (because you might need a __INLINE_CODE_138__ to be a __INLINE_CODE_139__ or for __INLINE_CODE_140__ only), so you can skip it, unless you have some logic to implement there.
Note that the __INLINE_CODE_141__ can be passed to any __INLINE_CODE_142__, even non-root, but __INLINE_CODE_143__ option is only valid for __INLINE_CODE_144__ Cycle and is ignored otherwise.
// RootCycle.js
import { run } from '@cycle/run';
import { makeHTTPDriver } from '@cycle/http';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const drivers = { HTTP: makeHTTPDriver() };
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
export default RootCycle;
__INLINE_CODE_145__ component is basically in charge of bootstrapping the "Cycle" part of your app here.
Then just wrap your __INLINE_CODE_146__ with it and e.g., render with __INLINE_CODE_147__:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import RootCycle from './RootCycle';
ReactDOM.render(
(
<RootCycle>
<App />
</RootCycle>
),
document.getElementById('root')
);
__INLINE_CODE_148__ option
You might have noticed in axamples above that we're not providing any component to a __INLINE_CODE_149__ HOC, leaving empty braces instead:
const ConnectedComponent = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
Well, that is not a mistake - the wrapped component is optional. When a "component to wrap" is provided to __INLINE_CODE_150__, then if we render the connected component, the __INLINE_CODE_151__ will also render __INLINE_CODE_152__ internally.
Thats all good and works (and what you'll be using most of the time anyway), but sometimes you just want to keep your React Element tree a bit cleaner when the wrapped component would be a kind of noop anyway (just forwarding __INLINE_CODE_153__ and nothing else), or component is some technical/lower-level like __INLINE_CODE_154__, or have some special rendering behavior.
So, we want to avoid seeing this in React Developer Tools:
<cycleConnect(SomeComponent)>
<SomeComponent>
...
</SomeComponent>
</cycleConnect(SomeComponent)>
You can use __INLINE_CODE_155__ option for that. It accepts the function with this signature:
(props) => ReactNode | null
If no component is provided for wrapping, nor __INLINE_CODE_156__ option is defined, the __INLINE_CODE_157__ option defaults to __INLINE_CODE_158__ function, so its either forwarding __INLINE_CODE_159__ if any or renders nothing.
If both __INLINE_CODE_160__ option is provided and component-to-wrap is passed, then __INLINE_CODE_161__ takes the precedence.
Note: Why not use the __INLINE_CODE_162__ option in place of wrapped component all the time? Well, its technically possible, espeically if your React component is a stateless functional component anyway.
But regular HOC wrapping is just common technique in React world and is nicely composable when you have other HOCs than __INLINE_CODE_163__.
And is cleaner too - you might be prototyping your app as a set of __INLINE_CODE_164__ components first, without even involving __INLINE_CODE_165__, then "connect" them all at once, or introduce __INLINE_CODE_166__ incrementally. Whatever :)
__INLINE_CODE_167__ option
Your component's __INLINE_CODE_168__ is by default automatically assigned to the following (depending on whether the wrapped component is provided, or __INLINE_CODE_169__ option, or neither:
- __INLINE_CODE_170__ when WrappedComponent is passed
- __INLINE_CODE_171__ when __INLINE_CODE_172__ prop is defined (and is truthy)
- __INLINE_CODE_173__ otherwise
You can override that like with any regular React component:
const ConnectedComponent = cycleConnect({
...
)(Component);
ConnectedComponent.displayName = 'ConnectedComponent';
But there's also a __INLINE_CODE_174__ option to set the generated (connected) component __INLINE_CODE_175__, which looks a bit cleaner:
const ConnectedComponent = cycleConnect({
...
displayName: 'ConnectedComponent'
)(Component);
Inside the Cycle
__INLINE_CODE_176__ really assumes that you know how to use Cycle.js. Its really a very simple yet very elegant pattern, so make sure to familiarize yourself with it.
Quick recap on what Cycle.js __INLINE_CODE_177__ function is. Its basically a pure function with the signature:
main :: (Sources) => Sinks
It accepts __INLINE_CODE_178__ as an argument and returns __INLINE_CODE_179__ and describes a dataflow using composition of reactive streams and their transformations with pure functions.
Please note that the __INLINE_CODE_180__ function is only run once per __INLINE_CODE_181__ component lifecycle. It doesn't run again on rerendering or something. This is because the actual call to __INLINE_CODE_182__ is to just build up a dataflow, not to get some rendering data.
The data flows through reactive streams, from __INLINE_CODE_183__ to __INLINE_CODE_184__ of the __INLINE_CODE_185__, until its __INLINE_CODE_186__ are unsubscribed from parent Cycle program's __INLINE_CODE_187__ (this happens when React component is unmounted).
Drivers
The __INLINE_CODE_188__ is supposed to run side effects for a Cycle.js program. Its outside the program itself, supposed to be impure and connects your Cycle app with the "outer world". This is an essential concept and is described in a great detail in Cycle.js docs on drivers.
There are no differences from idiomatic Cycle.js in how __INLINE_CODE_189__ handles __INLINE_CODE_190__ - it just hands it over to __INLINE_CODE_191__ directly. There is only one __INLINE_CODE_192__ driver (an __INLINE_CODE_193__ driver) that is always added internally by the __INLINE_CODE_194__ed component - it allows to invoke functions outside the __INLINE_CODE_195__ that is to be maintained pure.
Wrappers
The __INLINE_CODE_196__ is a rather simple thing - its just a function that takes __INLINE_CODE_197__ function that wraps it with the other __INLINE_CODE_198__ function that might in turn add some logic in between:
function someWrapper(originalMain) {
return wrappedMain(sources) {
// Can do something here, like transforming `sources` somehow
// ...
// Invoke the original `main` internally to obtain its sinks
const sinks = originalMain(sources);
// Can do something to `originalMain`s sinks here
// ...
return {
...sinks,
someOtherSink: xs.of({ ... }) // Can add a new sink
};
}
}
It is important that the wrapping __INLINE_CODE_199__ is maintained pure, so that your entire program is pure. If you need some effectful logic to run in a wrapping __INLINE_CODE_200__, you should consider using driver for that. Wrappers, just like any regular __INLINE_CODE_201__ function, can internally use __INLINE_CODE_202__ that are created from __INLINE_CODE_203__.
Wrappers can also add arbitrary __INLINE_CODE_204__ and could expect certain __INLINE_CODE_205__ to be returned from the Cycle program, depending on particular wrapper implementation.
Wrappers are usually created to encapsulate some non-business-logic stuff, you'd be normally describe anything related to the app itself in the "original" __INLINE_CODE_206__ function.
Most of __INLINE_CODE_207__ (aside from __INLINE_CODE_208__ HOC itself) is implemented with wrappers, that are created for each __INLINE_CODE_209__ed component automatically:
- __INLINE_CODE_210__ (to mess around with React component props)
- __INLINE_CODE_211__ (a gateway to React component lifecycle streams)
- __INLINE_CODE_212__ (to route the view events back from your wrapped React component aka view, and pass the events upstream, to parent components)
Sources
This is a Cycle.js program's input. __INLINE_CODE_213__ function shouldn't care whether something on the __INLINE_CODE_214__ is a __INLINE_CODE_215__ or some custom thing provided by one of the wrappers.
For __INLINE_CODE_216__ connected component, the __INLINE_CODE_217__ are __INLINE_CODE_218__ directly plus whatever __INLINE_CODE_219__ add to them.
Sinks
This is a Cycle.js program's output.
__INLINE_CODE_220__ invokes your __INLINE_CODE_221__ed component's __INLINE_CODE_222__ function, takes its __INLINE_CODE_223__ and subscribes the __INLINE_CODE_224__ passed from the parent component to these __INLINE_CODE_225__. Its how __INLINE_CODE_226__ is "attaching" the new component to a tree of Cycle programs.
If there's no parent component (thus, no __INLINE_CODE_227__) and your component is a "Root Cycle", then the sinks are handled to __INLINE_CODE_228__ to "run" as with a regular Cycle.js app.
React __INLINE_CODE_229__
The __INLINE_CODE_230__ed __INLINE_CODE_231__ component is essentially a view for the associated Cycle.js __INLINE_CODE_232__ function. And __INLINE_CODE_233__ encourages the view to be a "dumb" stateless function that takes __INLINE_CODE_234__ as an argument ad returns something of __INLINE_CODE_235__ type (i.e., __INLINE_CODE_236__s, __INLINE_CODE_237__, etc, whatever __INLINE_CODE_238__ component renders).
Note: The wrapped __INLINE_CODE_239__ shouldn't necessarily be a stateless functional component (SFC), you could have any __INLINE_CODE_240__ component wrapped, but its advisable to use functions for the most part as the whole point of __INLINE_CODE_241__ is to use __INLINE_CODE_242__ for anything that is not a view.
So, the __INLINE_CODE_243__ is the only communication channel from our Cycle.js part (the connected component's __INLINE_CODE_244__ function) to connected __INLINE_CODE_245__ component itself. We therefore need to pass the props from our Cycle program to the view (a __INLINE_CODE_246__ component) somehow.
This is where __INLINE_CODE_247__ comes into play and provides __INLINE_CODE_248__ source and sink for a __INLINE_CODE_249__ function. Its local to a component, so no other components will share the same __INLINE_CODE_250__ source or sink.
__INLINE_CODE_251__ sink
The __INLINE_CODE_252__ sink on __INLINE_CODE_253__ed is there to pass the props to the view:
function main(sources) {
return {
props: xs.of({ greetable: 'World' })
};
}
// Use the passed `greetable` prop
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
Remember, that the value you map to __INLINE_CODE_254__ sink should be an object of new props and that updated props will be merged with existing component __INLINE_CODE_255__ like:
const updatedProps = Object.assign({}, prevProps, newProps);
This means that you can easily overwrite any props you want and leave the others intact.
Also, note above that we're passing the __INLINE_CODE_256__ through an __INLINE_CODE_257__ stream and that might be synchronous (like in our example with a stream of static data that is sent immediately) but could also be asynchronous. In that case the value of some prop might be not yet available on the first render:
function main(sources) {
return {
props: xs.of({ greetable: 'World' }).compose(delay(3000))
};
}
// Trying to use the passed `greetable` prop
// But whooops, no `greetable` yet :(
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
So either provide a default value for that async prop's __INLINE_CODE_258__ or perform additional checks in the view, when rendering.
Note: When messing with the __INLINE_CODE_259__ in your __INLINE_CODE_260__, remember that any props that are passed from outside as an input to a __INLINE_CODE_261__ component, are also there (including callback functions, if there are any) and are also merged upon updates from outside (on __INLINE_CODE_262__).
Lets summarize: the __INLINE_CODE_263__ from the __INLINE_CODE_264__ sink are passed from the __INLINE_CODE_265__ function to the view (__INLINE_CODE_266__ component).
__INLINE_CODE_267__ source
Not only you'll need to provide the props to the view for rendering, but oftentimes you'd need your __INLINE_CODE_268__ to take part in transformations/calculations in the __INLINE_CODE_269__ function itself. For that reason, __INLINE_CODE_270__ provides the __INLINE_CODE_271__ source as well.
It allows you to have an __INLINE_CODE_272__ stream of your component's input __INLINE_CODE_273__. Note that the __INLINE_CODE_274__ is of type __INLINE_CODE_275__ and to take the actual stream of props, use its __INLINE_CODE_276__ attribute:
function main(sources) {
const name$ = sources.props.props$.map(props => props.name);
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
function MyComponent(props) {
return (
<div>
Hello, {props.name}! Your father is Mr. {props.father}.
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
...
// Somewhere else:
<MyConnectedComponent name="Luke Skywalker" father="Darth Vader" />
// renders:
//
// <div>
// Hello, LUKE SKYWALKER! Your father is Mr. Darth Vader.
// </div>
The new value on __INLINE_CODE_277__ is emitted every time __INLINE_CODE_278__ change. This could be overkill for the most cases, as you would only need just few props for your dataflow.
This is what __INLINE_CODE_279__ method for, it accepts the name of the nested prop (will return __INLINE_CODE_280__ if the stream value is not an object) and returns a stream of that prop only that is only got new value once this particular prop changes:
function main(sources) {
const name$ = sources.props.pluck('name');
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
The __INLINE_CODE_281__ed stream also performs shallow comparison and does __INLINE_CODE_282__ behind the scenes, so identical values don't cause a new value emitted on an __INLINE_CODE_283__.
Note that you don't need to always pull all props from your __INLINE_CODE_284__ function's __INLINE_CODE_285__ just for the sake of it to just forward to the __INLINE_CODE_286__ sink directly, you better only pick those you need in a dataflow with __INLINE_CODE_287__. The __INLINE_CODE_288__ passed from outside will be available on the view normally. You can overwrite them from inside your __INLINE_CODE_289__ though.
There's also __INLINE_CODE_290__ method that also accepts a nested prop name. The difference is that the result is not an __INLINE_CODE_291__ stream of prop values but the __INLINE_CODE_292__ instance itself that is scoped to that piece of __INLINE_CODE_293__:
function main(sources) {
const name$ = sources.props.pluck('name'); // Stream of strings
const nameSource = sources.props.select('someNestedObject'); // Scoped ReactPropsSource
const someNestedObjProperty$ = nameSource.pluck('someNestedObjectProperty');
...
}
This is useful when you want to __INLINE_CODE_294__ a stream of some sub-prop from deeper nested objects.
Note: Taking deep property stream this way isn't really nice currently as requires quite a bit of boilerplate - this part of API is already being thought out further and reconsidered.
Interactions
So far so good, but rendering the __INLINE_CODE_295__ isn't the only exciting thing we want to do with __INLINE_CODE_296__, right? We also want to handle events coming from the view(e.g., the DOM).
The way __INLINE_CODE_297__ handles events and nested components' callbacks is the biggest deviation from an idiomatic Cycle.js app using __INLINE_CODE_298__ driver. It refrains from a holistic "no handlers in the view" approach for a few reasons:
- We need something more in line with __INLINE_CODE_299__ as we're using React here
- We're not always handling DOM events but also need to handle some children components data/events passed back. We can't pass streams like in regular Cycle.js app, in __INLINE_CODE_300__ we do it via callbacks that are provided amongst children components' __INLINE_CODE_301__ and we need __INLINE_CODE_302__ed components to be compatible __INLINE_CODE_303__ components at all levels.
This is what "interactions" feature of __INLINE_CODE_304__ is for. It provides the unified interface for parent-child components communication and event handling that is aligned with a regular event/callback handling model on __INLINE_CODE_305__ side but also allows to access those events in form of __INLINE_CODE_306__ streams inside the __INLINE_CODE_307__ function.
Events triggering by the view
Every __INLINE_CODE_308__ component has some additional __INLINE_CODE_309__-specific props injected, so they are available on your view.
These are __INLINE_CODE_310__ which is a function that takes the __INLINE_CODE_311__ (a string) and optional __INLINE_CODE_312__ (of any type) and returns a function that will be a callback for your event or some child component prop function.
So, the signature is:
(interactionType, predefinedValue?) => (value) => void
Its supposed to be called imperatively in your view and is available as __INLINE_CODE_313__:
function MyComponent({ interact }) {
return (
<div>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
For the sake of being cleaner (and for better TypeScript support), __INLINE_CODE_314__ also leverages the ES2015 __INLINE_CODE_315__ feature to provide the object with dynamic properties, each representing an __INLINE_CODE_316__ with __INLINE_CODE_317__ dynamically defined after the function property name. Its available on __INLINE_CODE_318__ so we can rewrite the above snippet as:
function MyComponent({ interactions }) {
return (
<div>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
In the example above, the __INLINE_CODE_319__ function receives the click __INLINE_CODE_320__ as an argument and that is what you'll get in your __INLINE_CODE_321__ (next section).
While cleaner, it doesn't allow for the __INLINE_CODE_322__ to be set, so if you need some particular value to be emitted by the associated stream in your __INLINE_CODE_323__, you can either use __INLINE_CODE_324__ or create an inline function:
// Assuming you have `id` available, e.g.
// when looping/mapping over the collection of items
<button
type="button"
className="delete-btn"
onClick={interact('delete', id)}
>
Click me!
</button>
// OR
<button
type="button"
className="delete-btn"
onClick={() => interactions.delete(id)}
>
Click me!
</button>
View events handling with __INLINE_CODE_325__ source
There's internal __INLINE_CODE_326__ in __INLINE_CODE_327__ and it provides the __INLINE_CODE_328__ source for our __INLINE_CODE_329__ function. The __INLINE_CODE_330__ source is of type __INLINE_CODE_331__, which has the __INLINE_CODE_332__ method, which we can use to obtain interaction streams:
function main(sources) {
const buttonClick$ = sources.interactions.select('buttonClick');
// Do something with a buttonClick$, e.g.
// map it to the click coordinates props
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interact, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
The streams are __INLINE_CODE_333__ed by the same __INLINE_CODE_334__ string you provided to the __INLINE_CODE_335__ function on your view.
Similar to how __INLINE_CODE_336__ object with dynamic function-props is available on your view, __INLINE_CODE_337__ is a ES2015 __INLINE_CODE_338__ and it maintains the dynamic streams right on itself (just don't use __INLINE_CODE_339__ as its reserved method name here here):
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interactions, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
Note: If you try to get the non-existing stream, it will still work, this thing is dynamic, so the stream will be created nevertheless but will just never emit values. Be careful and look out for typos! Using TypeScript is highly recommended to avoid typos here.
Upstream interactions with __INLINE_CODE_340__ sink (calling callbacks on __INLINE_CODE_341__).
Now that we can handle the streams of view events and values from the children components, we would also need to somehow call the callbacks that are passed among our __INLINE_CODE_342__.
First, nothing prevents from following the common React way and call those directly from the view:
function MyComponent({ onButtonClick }) {
return (
<button type="button" onClick={onButtonClick}>
Click me!
</button>
);
}
// Somewhere else:
doSomething = (event) => { console.log('clicked!'); };
...
<MyComponent onButtonClick={doSomething} />
This is fine if you don't need that value's stream in your dataflow. This approach bypasses your Cycle.js program's __INLINE_CODE_343__ completely and doesn't make it impure by otherwise possibly passing an effectful function as __INLINE_CODE_344__. There's even no need to __INLINE_CODE_345__ your component in the above example.
Keep in mind that if __INLINE_CODE_346__ is optional there, you will also need to check whether that is defined before providing that as a callback (use __INLINE_CODE_347__ or TypeScript to mark it as either required or optional).
Oftentimes you will need to call some callback that lives on the __INLINE_CODE_348__ though, and this is what __INLINE_CODE_349__ sink provided by __INLINE_CODE_350__ does:
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
const coords$ = buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
);
// We both update the `props` (to render it in this component)
// and pass upstream
return {
props: coords$,
interactions: xs.of({
onCoordsChange: coords$
})
};
}
function MyComponent({ interactions }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
handleCoordsChange = (coords) => {
console.log('New coordinates received: ', coords);
};
...
<ConnectedComponent onCoordsChange={handleCoordsChange} />
// The new coords will be rendered by the component
// and the console will show the "New coordinates received"
// with new coords too.
Once the appropriate stream emits a value, that callback is called. It will also check if the callback was passed in the first place to skip the call attempt if no callback with a given name is passed among the __INLINE_CODE_351__.
Important: Note that __INLINE_CODE_352__ sink expects a stream of object whose property names correspond to callback names on __INLINE_CODE_353__ and property values are themselves streams too - whatever values those streams emit, will be passed as an argument to the callback.
So far so good, but we know that our __INLINE_CODE_354__ is a wrapper and not a driver, so what if one passed the effectful function as a callback, e.g., __INLINE_CODE_355__ passed as __INLINE_CODE_356__ above does a __INLINE_CODE_357__ internally - that is a side effect, right?
Well, this is why the function is not invoked directly, or inside the wrapper (since the wrapper is the part of the pure land of our __INLINE_CODE_358__s), its using the __INLINE_CODE_359__ driver internally. More on this below in a section about Side-effect control, just keep in mind that __INLINE_CODE_360__ and __INLINE_CODE_361__ should be pure and __INLINE_CODE_362__ follows that requirement.
React component lifecycle streams
You're probably not going to need __INLINE_CODE_363__ component lifecycle hooks often inside your __INLINE_CODE_364__, but still - they are provided via special __INLINE_CODE_365__. It provides the __INLINE_CODE_366__ source that is available on each __INLINE_CODE_367__ed __INLINE_CODE_368__ sources.
The lifecycle events are provided as __INLINE_CODE_369__ streams and their names mostly mirror the React lifecycle methods, but shortened to remove some redundant __INLINE_CODE_370__ prefixes:
React lifecycle method
__INLINE_CODE_371__ source property
Stream value
__INLINE_CODE_372__
__INLINE_CODE_373__
__INLINE_CODE_374__
__INLINE_CODE_375__
__INLINE_CODE_376__
__INLINE_CODE_377__
__INLINE_CODE_378__
__INLINE_CODE_379__
__INLINE_CODE_380__
__INLINE_CODE_381__
__INLINE_CODE_382__
__INLINE_CODE_383__
__INLINE_CODE_384__
__INLINE_CODE_385__
__INLINE_CODE_386__
__INLINE_CODE_387__
__INLINE_CODE_388__
__INLINE_CODE_389__
__INLINE_CODE_390__
__INLINE_CODE_391__
Error data
The "error data" on the __INLINE_CODE_392__ is an object of this shape:
{
error: Error;
errorInfo: ErrorInfo;
}
These are basically React __INLINE_CODE_393__ arguments squashed into a single object.
Also, note that __INLINE_CODE_394__ hook is not present there, since that is not really an event and affects how your component is rendered. __INLINE_CODE_395__ would be providing its own custom internal __INLINE_CODE_396__ instead for optimisation purposes and you could implement it on your wrapped component anyway.
Note: As well, no lifecycle events are passing anything related to React component __INLINE_CODE_397__, only taking __INLINE_CODE_398__ into account instead. This is because the __INLINE_CODE_399__ is meant to be managed differently when Cycle.js is in use. You can still use that for wrapped component but its out of play on the component generated by __INLINE_CODE_400__.
A quick example:
function main(sources) {
const willUnmount$ = { sources.lifecycle };
// Do something on unmounting, e.g. report that to a console
// via some hypothetical `logger` driver
return {
logger: willUnmount$.mapTo(`I'll be unmounted soon!`)
};
}
Side-effect control and __INLINE_CODE_401__ driver
Side-effect control is where Cycle.js really shines (not only implementation, it applies to the "cycle pattern" behind Cycle.js in the first place).
In order to keep the __INLINE_CODE_402__ part of __INLINE_CODE_403__ed component pure, we need to make sure that any potentially effectful function is not run inside our __INLINE_CODE_404__ directly.
This is what __INLINE_CODE_405__ driver is responsible for. It allows to only describe what function you want to call and with what arguments, then delegate that to a __INLINE_CODE_406__ which is supposed to run any impure functions outside of the app itself.
The way it works is very similar to that of __INLINE_CODE_407__ driver from __INLINE_CODE_408__ package where you don't actually run the request inside your __INLINE_CODE_409__ but just describe it and pass to __INLINE_CODE_410__ driver to handle. __INLINE_CODE_411__ driver is just a bit more generic and operates on functions.
__INLINE_CODE_412__ uses __INLINE_CODE_413__ driver internally (to run effects for upstream __INLINE_CODE_414__), but it is also available for you on any connected __INLINE_CODE_415__ as __INLINE_CODE_416__ sink automatically (i.e., without explicit configuration on a root cycle):
function log() {
console.log(`I'm effectful!`);
}
function saveToLocalStorage(message) {
localStorage.setItem('message', message);
}
function main(sources) {
const someCallbackFn$ = sources.props.pluck('someCallbackFunction');
return {
fnCallEffect: xs.merge(
xs.of({ fn: log }),
xs.of({ fn: saveToLocalStorage, args: ['some message to save'] }),
// Call the `props` callback function directly, this is how
// `interactions` sink works behind the scenes:
someCallbackFn$.map(fn => ({ fn }))
)
};
}
Isolation
Isolation is Cycle.js way of making the component's __INLINE_CODE_417__ and __INLINE_CODE_418__ isolated from influence of other components.
Make sure to carefully read more about Cycle.js isolation here. It might take a bit to get used to an idea.
To reiterate: the concept of isolation is important for Cycle.js apps. Its a little bit different for __INLINE_CODE_419__ because most things are scoped to a particular __INLINE_CODE_420__ed component already, like components have "local" __INLINE_CODE_421__, __INLINE_CODE_422__ and __INLINE_CODE_423__ sources and sinks and __INLINE_CODE_424__ components are also self-contained already.
There are though __INLINE_CODE_425__/__INLINE_CODE_426__ that will need the isolation feature, like __INLINE_CODE_427__ driver or __INLINE_CODE_428__ wrapper (a state management solution for Cycle.js apps). So, isolation makes sense for shared __INLINE_CODE_429__/__INLINE_CODE_430__, those you pass to __INLINE_CODE_431__ function (by defining the __INLINE_CODE_432__/__INLINE_CODE_433__ options on root __INLINE_CODE_434__) and that will be available across the entire tree of Cycle programs.
Note: The isolation works by scoping the source streams to particular component and a kind of unscoping of the sink streams before connecting to parent's sinks.
Also note, that each driver/wrapper decides on its own how its sources/sinks should be isolated.
In Cycle.js, you use the __INLINE_CODE_435__ function from __INLINE_CODE_436__ package directly, as you're just invoking the children __INLINE_CODE_437__ functions from your current component's __INLINE_CODE_438__ function:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// Do something with childSinks, combine somehow, etc
return {
...
};
}
__INLINE_CODE_439__ function has this simple signature:
isolate :: (MainFn, scope) => MainFn
It takes the __INLINE_CODE_440__ function and an optional __INLINE_CODE_441__ and returns an isolated __INLINE_CODE_442__ function.
__INLINE_CODE_443__ does also use the __INLINE_CODE_444__ package internally, but you can't call __INLINE_CODE_445__ directly on some __INLINE_CODE_446__ function. You'd never call one component's __INLINE_CODE_447__ from the other component's __INLINE_CODE_448__ directly with __INLINE_CODE_449__.
Naive explicit isolation (conceptually incorrect)
Just to illustrate the issue, there's an attempt to isolate the component's __INLINE_CODE_450__ sources, so that HTTP requests initiated from that components are scoped to it (and are unavailable to siblings).
Since we know that __INLINE_CODE_451__ returns a __INLINE_CODE_452__ function and __INLINE_CODE_453__ takes the __INLINE_CODE_454__ function as an argument, lets try to feed it an isolated version of it:
function main(sources) {
...
}
function MyComponent() {
...
}
const isolatedMain = isolate(main, { HTTP: 'myComponent' });
const ConnectedComponent = cycleConnect(isolatedMain)(MyComponent);
While this will won't raise any errors (because this is valid code after all), this approach brings a subtle issue.
This will isolate things for this particular components but not their children components, so in this case, children (connected childred __INLINE_CODE_455__ components) will have no __INLINE_CODE_456__ isolation scope for __INLINE_CODE_457__ channel.
This might seems like a no-issue, because hey, you can define different scopes for the children components right? Well, while this is technically possible, its conceptually incorrect and it will be completely broken in some cases, e.g., with __INLINE_CODE_458__ where the deeper you go - the "more filtered" source streams you get.
Important! The above note about it being "conceptually incorrect" refers to the way __INLINE_CODE_459__ maintains the tree of Cycle.js programs internally. It tries to make the entire app structure look the same way as you'd manually write a Cycle.js app. So, when you __INLINE_CODE_460__ at some level, then if inside your __INLINE_CODE_461__ you also call some __INLINE_CODE_462__ happens, then __INLINE_CODE_463__ gets isolated sources no matter what.
__INLINE_CODE_464__ option for __INLINE_CODE_465__
The first way to correctly isolate __INLINE_CODE_466__ed component is to use the __INLINE_CODE_467__ option for __INLINE_CODE_468__ itself, so the above can be rewritten as:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main, {
isolate: { HTTP: 'myComponent' }
})(MyComponent);
This will work perfectly, with all children __INLINE_CODE_469__ed components having __INLINE_CODE_470__ namespace applied to __INLINE_CODE_471__ sources/sinks in their __INLINE_CODE_472__ functions.
The __INLINE_CODE_473__ option accepts a __INLINE_CODE_474__ or the __INLINE_CODE_475__ and in that case its passed directly to __INLINE_CODE_476__ package's __INLINE_CODE_477__ as a __INLINE_CODE_478__ argument. So, check out the __INLINE_CODE_479__ function API reference.
__INLINE_CODE_480__ also makes it accept a function that takes component __INLINE_CODE_481__ as an argument and should return the scope of __INLINE_CODE_482__ or __INLINE_CODE_483__ type that is then also passed to __INLINE_CODE_484__ package's __INLINE_CODE_485__. This allows for some dynamic isolation scopes to be set based on the React component's initial props.
Important: Despite the __INLINE_CODE_486__ is provided as an option to __INLINE_CODE_487__ when defining the connected component, the actual isolation will only happen when component instance is created. This is why its possible to dynamically resolve the __INLINE_CODE_488__ based on the component __INLINE_CODE_489__ in __INLINE_CODE_490__.
__INLINE_CODE_491__ prop
Despite the __INLINE_CODE_492__ option will work nicely for some cases, there's a pattern in how it is utilized in pure Cycle.js programs. Lets take a look at pure Cycle.js example again:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// ...
return {
...
};
}
The important part is that in Cycle.js app its not a child who decides how to isolate itself but __INLINE_CODE_493__ is provided from the parent instead.
Most of the time, we want to maintain the same approach with __INLINE_CODE_494__. The advantage is that component in this case is totally agnostic from the provided scope, it doesn't even care whether its isolated or not at all - all it knows is that it expects certain __INLINE_CODE_495__ and returns certain __INLINE_CODE_496__. This makes the component reusable.
This is especially important with __INLINE_CODE_497__, where you want to control where your child components "attach" to the state.
In other words, the parent decides how to compose children components, not the other way around, because otherwise the child would need to have an intimate knowledge of something that really belongs to parent and thus not being reusable and breaking encapsulation.
For this reason, every __INLINE_CODE_498__ed React component accepts an __INLINE_CODE_499__ prop, so you can use that just like any other prop:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
// Isolate everything
<ConnectedComponent isolate="myComponent" />
// Isolate HTTP only
<ConnectedComponent isolate={{ HTTP: 'myComponent' }} />
<ConnectedComponent isolate={{ HTTP: 'myComponent_2' }} />
In contrast with __INLINE_CODE_500__ option, the __INLINE_CODE_501__ prop can only accept __INLINE_CODE_502__ or __INLINE_CODE_503__ that is directly passed to __INLINE_CODE_504__ function from __INLINE_CODE_505__, thus maintaining full parity with pure Cycle.js approach. Indeed, having a function that accepts initial __INLINE_CODE_506__ wouldn't make much sense because you're anyway providing props that could be different for different component instances when rendering that component.
__INLINE_CODE_507__ helper component
Since isolation in __INLINE_CODE_508__ applies to both "current" __INLINE_CODE_509__ed component and all its __INLINE_CODE_510__ed children, we might sometimes need to define some connected component to only be a common parent for some children, which must provide isolation but wouldn't contain any logic.
This is what a helper __INLINE_CODE_511__ component for. It accepts the __INLINE_CODE_512__ prop (that is directly passed as a __INLINE_CODE_513__ argument to __INLINE_CODE_514__ function of __INLINE_CODE_515__):
<Isolator scope={{ HTTP: 'someNamespace' }}>
<ConnectedComponent someProps="1" />
<ConnectedComponent someProps="2" />
<ConnectedComponent someProps="3" />
</Isolator>
This assists the isolation composition at view level.
Extra: State management with __INLINE_CODE_516__
__INLINE_CODE_517__ is the idiomatic Cycle.js fractal state management solution. It is tailored for Cycle.js apps but since __INLINE_CODE_518__ is using the real Cycle.js, it stays fully compatible here as well.
Using __INLINE_CODE_519__ with __INLINE_CODE_520__ is optional, since you might be using different lib (e.g., __INLINE_CODE_521__) for that.
To start, just read the onionify docs and install the package:
$ npm install --save cycle-onionify
and pass that with __INLINE_CODE_522__ array to a __INLINE_CODE_523__ connected component (e.g., Root Cycle):
// RootCycle.js
import { run } from '@cycle/run';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
wrappers
})();
export default RootCycle;
Now every __INLINE_CODE_524__ed component that is a child of the above-mentioned __INLINE_CODE_525__ in React component tree got the __INLINE_CODE_526__ source and sink.
Important: Notice that you're all set - its purely a Cycle.js land. __INLINE_CODE_527__ doesn't provide any additional functionality for __INLINE_CODE_528__. Instead, it provides few helpers for isolation and working with collections on the React side.
All the exportables related to __INLINE_CODE_529__ reside in the extra package: __INLINE_CODE_530__. It is installed along with the main lib but is not __INLINE_CODE_531__d/__INLINE_CODE_532__ed by default, so won't be bundled (by Webpack, Rollup, etc) unless you're using it.
This extra sub-package contains few things:
- __INLINE_CODE_533__ helper component
- __INLINE_CODE_534__ helper component
- __INLINE_CODE_535__ helper factory function
Read ahead for details on each.
In __INLINE_CODE_536__, the state/reducers layer composition is done via isolation, so the first thing here is __INLINE_CODE_537__:
__INLINE_CODE_538__ helper component
This is pretty much like __INLINE_CODE_539__ but is here to specifically isolate __INLINE_CODE_540__ sources/sinks only.
So, the:
import { StateIsolator } from 'react-cycle-connect/lib/extra/onionify';
// ...
<StateIsolator lens="someStateProperty">
<NestedComponent />
</StateIsolator>
is somewhat a convenient shortcut to:
import { Isolator } from 'react-cycle-connect';
// ...
<Isolator scope={{ onion: 'someStateProperty', '*': null }}>
<NestedComponent />
</Isolator>
Notice how __INLINE_CODE_541__'s prop is explicitly named __INLINE_CODE_542__. It just implies that oftentimes you'll be using "lens" to isolate the state. Consider a string just a shortcut to a more elaborate __INLINE_CODE_543__ object.
In the example above, the __INLINE_CODE_544__'s __INLINE_CODE_545__ function __INLINE_CODE_546__ will represent only that "someStateProperty" piece of state. Pretty much how you'd __INLINE_CODE_547__ the __INLINE_CODE_548__ channel in an idiomatic Cycle.js app.
There's also a __INLINE_CODE_549__ property, so that you can change the name of the source/sink inside your __INLINE_CODE_550__ to something else. Thats to be on par with __INLINE_CODE_551__ features, where you can also change that name.
Handling collection state manually
Managing collections of components in pure Cycle.js with __INLINE_CODE_552__ is a kind of a recognized challenge. This is why __INLINE_CODE_553__ ended up providing __INLINE_CODE_554__ helper function - see their docs for more detail.
While __INLINE_CODE_555__ also has a __INLINE_CODE_556__ helper component that is very similar to __INLINE_CODE_557__ __INLINE_CODE_558__ function, lets first try to pretend we don't have it and imagine how'd we handle the state without it.
Note: Nothing prevents you from skipping right to a description of how __INLINE_CODE_559__ works, but it might be useful to understand the rationale behind it, so reading this section is recommended.
Handling collections is different with React than in Cycle.js, e.g., you can always just render React components in a loop or __INLINE_CODE_560__ an array of data to an array of components and React will render that.
When you want to render some __INLINE_CODE_561__ data from your state, its easy without any helpers:
/*
Assume you have this value on your `List` component `stateFork
- updated to more recent version of cycle, including @cycle/state
- updated to React 16.9 api, including new lifecycles and context api
- merged onionify separate lib so import is __INLINE_CODE_0__
- added tests
- published at @iilab/react-cycle-connect
react-cycle-connect
An experimental library that allows using Cycle.js as a reactive dataflow framework for React.
Written in TypeScript.
This little thing allows to define your app's view layer using regular React components, but implement the actual logic behind it using pure functional reactive approach, taking advantage of Cycle.js' Model-View-Intent pattern and its exceptional side effect control.
This isn't a custom implementation of Cycle pattern for __INLINE_CODE_1__ but a link to real Cycle.js instead, so any Cycle.js drivers and wrappers can be applied (e.g., __INLINE_CODE_2__ for state management). Also, for this reason, some knowledge about how __INLINE_CODE_3__ works is assumed.
Note: This is an experimental lib and currently works with __INLINE_CODE_4__ and __INLINE_CODE_5__ only. If all goes good, the support for __INLINE_CODE_6__/__INLINE_CODE_7__ (instead of __INLINE_CODE_8__) and for __INLINE_CODE_9__/__INLINE_CODE_10__ (instead of __INLINE_CODE_11__) is anticipated. Nothing prevents from having __INLINE_CODE_12__ covered too.
UPDATE (MAR 2018): Some internals overhaul is pending and it's currently waiting for Cycle.js to settle on hybrid push-pull reactivity and have internals rewritten to use Callbags instead of __INLINE_CODE_13__. So, __INLINE_CODE_14__ is going to be using Callbags by default too.
Any feedback is highly appreciated!
Table of Contents
- react-cycle-connect
- Table of Contents
- Shup up and show me the code
- Installation
- Motivation
- How it works (architecture)
- Guide
- API reference
- Alternatives / Competitors
- FAQ
- Roadmap
Shup up and show me the code
Cycle.js's counter example ported over:
import React from 'react';
import ReactDOM from 'react-dom';
import xs from 'xstream';
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
// Your Cycle.js `main` function
function main(sources) {
const action$ = xs.merge(
sources.interactions.decrement.map(ev => -1),
sources.interactions.increment.map(ev => +1)
);
const count$ = action$.fold((acc, x) => acc + x, 0);
return {
props: count$.map(count => ({ count }))
};
}
// Your React component
function App({ interactions, count }) {
return (
<div>
<button onClick={interactions.decrement}>Decrement</button>
<button onClick={interactions.increment}>Increment</button>
<p>Counter: {count}</p>
</div>
);
}
// Some connection magic
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
// Render normally as it's a regular React app
ReactDOM.render(
<ConnectedApp />,
document.getElementById('root')
);
Also - there's official TodoMVC example with __INLINE_CODE_33__ for state management.
Installation
Using npm:
$ npm install --save react-cycle-connect
Using yarn:
$ yarn add react-cycle-connect
Motivation
The motivation to create this lib was based off the need to retain the React-style composability of the view layer using regular React components, reuse existing React components, make it (the lib) easily/incrementally adoptable in an ongoing React project, but also take advantage of the Cycle.js' MVI pattern and side effect control.
At the same time, while the most valuable thing from __INLINE_CODE_34__ is the pattern itself, there are numerous available libs/plugins for Cycle.js to take advantage of, letting us take the best of both worlds.
This way, you can still use anything from React stack (e.g., __INLINE_CODE_35__ to compose your routed app screens), but describe the interactions as a dataflow, with pure functions and reactive streams. Most of React components are going to be "dumb" (and also stateless functional aka SFC) since all the backing logic and cross-component communication would be handled by a tree of __INLINE_CODE_36__ programs.
Why not just use Cycle.js alone?
That is what I'd recommend considering in the first place. Its fast, its cool, the __INLINE_CODE_37__ (virtual DOM rendering engine behind Cycle's DOM driver) is goddamn fast and reliable. If you don't intend to use __INLINE_CODE_38__ components in any way, don't even get bothered. The __INLINE_CODE_39__ is more about "introducing Cycle.js to __INLINE_CODE_40__", not the other way around.
Is it a "Cycle.js in React" or "React in Cycle.js"?
More of a "Cycle.js in React". The app is bootstrapped by rendering a React component to the DOM, view is composed the React way. The root Cycle.js program is being __INLINE_CODE_41__ by rendering a root __INLINE_CODE_42__ed component. Though, it doesn't prescribe how you write your Cycle.js programs, and aside from the DOM generation part, its just Cycle.js all the way.
How it works (architecture)
Every __INLINE_CODE_43__ed component got a real Cycle.js program "attached" to it. The boundary between the two is __INLINE_CODE_44__. From your Cycle.js program standpoint, __INLINE_CODE_45__ is a view. Cycle.js program can update __INLINE_CODE_46__ that React component will then render.
A bunch of nested React components in your app represent a "tree of React components". __INLINE_CODE_47__ maintains a "tree of Cycle.js programs" that map to __INLINE_CODE_48__ed components and takes advantage of React's context feature to provide links between the nodes. Each connected component provides scoped links to the children nodes .
New "Cycle nodes" are attached lazily, upon associated React component initialization (this is where React controls how Cycle programs are run). The above-mentioned "link" is just an object on React __INLINE_CODE_49__ that brings couple properties:
- __INLINE_CODE_50__ for the child connected component's __INLINE_CODE_51__ function; and
- __INLINE_CODE_52__ to subscribe to that component's __INLINE_CODE_53__ function __INLINE_CODE_54__.
This is where something unusual for the Cycle.js happens - we subscribe to and unsubscribe from connected component's __INLINE_CODE_55__ function __INLINE_CODE_56__ based on React component's lifetime hooks. In Cycle.js, you normally just run the app at startup and let it run as an ongoing process. But this is where __INLINE_CODE_57__ performs the sync between the two worlds.
Overall, aside from using React __INLINE_CODE_58__ (which is just an implementation detail) and some intense wrapping on the __INLINE_CODE_59__ side to make laziness possible, __INLINE_CODE_60__ tries to represent the entire tree of Cycle.js programs as close as possible to what you'd end up with by writing idiomatic Cycle.js with explicit calling of nested component's __INLINE_CODE_61__ functions from the parent component __INLINE_CODE_62__ function. The result of children components is always __INLINE_CODE_63__d.
Aside from passing the __INLINE_CODE_64__ to children components, __INLINE_CODE_65__ also explicitly applies some internal wrappers to integrate with React component: __INLINE_CODE_66__, __INLINE_CODE_67__ and __INLINE_CODE_68__, that are scoped to a connected component's Cycle.js program only. It also explicitly runs the internal __INLINE_CODE_69__ driver for the __INLINE_CODE_70__ nodes by default - this is necessary for the __INLINE_CODE_71__ sink to work (because its supposed to run callbacks passed via __INLINE_CODE_72__ and that might be an effectful function).
Note that the entire internal "tree" of Cycle.js programs is maintained pure, including internal component-scoped wrappers, just as you would do with regular Cycle.js program.
Differences from idiomatic Cycle.js usage
Note: there's a difference in how'd you write the __INLINE_CODE_73__/__INLINE_CODE_74__ app vs pure __INLINE_CODE_75__ app. There's no misuse of Cycle.js per se, we just don't use it for rendering with __INLINE_CODE_76__ - and that applies certain implications to the approach.
__INLINE_CODE_77__ and __INLINE_CODE_78__, while both allow to create user interfaces, imply different approaches to the app architecture. The most important part here is that they are conceptually different when it comes to generating the VDOM.
In order to combine the two approaches nicely, there are certain deviations from how'd you write the __INLINE_CODE_79__-only app to allow for more optimal React DX.
The main difference from a pure __INLINE_CODE_80__ way is that its not a Cycle program that technically generates VDOM - instead, there's React component to represent a piece of UI. __INLINE_CODE_81__ approach to view/__INLINE_CODE_82__ event handling is rather purist - view only defines the markup and nothing else (i.e., user interaction/event handlers are defined inside Cycle program itself and not as some view component __INLINE_CODE_83__/etc attribute). That is not a crazy random opinion but instead a very well thought and weighted out decision by Cycle.js team. __INLINE_CODE_84__ takes a different approach, which isn't that strict and is more in line with React, with callbacks amongst __INLINE_CODE_85__ and all that stuff. It enables the events/component callbacks handling via interactions.
The view composition is obviously completely different. In __INLINE_CODE_86__ you run each component's __INLINE_CODE_87__ function that (possibly) generates a VDOM node and then explicitly combine the pieces of markup into a single VDOM stream that is then given to a DOM driver. With __INLINE_CODE_88__ you compose the view layer with React components normally. You can just connect those to Cycle.js program to "outsource" the actual logic to it.
Guide
This assumes the preliminary knowledge of both __INLINE_CODE_89__ and __INLINE_CODE_90__ (and __INLINE_CODE_91__). It doesn't provide much of the insight on how these work, but is instead focused on how __INLINE_CODE_92__ combines the two.
Basics
The result of __INLINE_CODE_93__ call is a HOC that optionally takes a React component (similar to that __INLINE_CODE_94__ function of __INLINE_CODE_95__) and returns a component that is augmented with backing Cycle.js program. This Cycle.js program is internally wrapped into a "Cycle Node" that, when initialized, is connected to a tree of Cycle.js programs. "Cycle Nodes" of every __INLINE_CODE_96__ed components are linked together via React __INLINE_CODE_97__.
Note, though, that there are no direct references from one component's Cycle program to another component's Cycle program, i.e., you're not invoking one __INLINE_CODE_98__ function from the other (this is perfectly possible though, but since we're not using __INLINE_CODE_99__ here, it would only handle non-DOM stuff). Instead, __INLINE_CODE_100__ handles the linking behind the scenes, so the "sibling" Cycle programs are fed with the same __INLINE_CODE_101__ and subscribe their __INLINE_CODE_102__ to the same __INLINE_CODE_103__.
There's essential concept of isolation in Cycle.js, so every __INLINE_CODE_104__ed component accepts an __INLINE_CODE_105__ prop, that is directly passed to __INLINE_CODE_106__ function from __INLINE_CODE_107__ package. Read more about Cycle.js isolation here.
Root Cycle
Every __INLINE_CODE_108__ed component provides a __INLINE_CODE_109__ object to its children through React __INLINE_CODE_110__ and also expects that __INLINE_CODE_111__ from some parent component (not necessarily immediate parent).
That link object has the following shape:
{
// Sources to provide to our Cycle.js program's `main` function as an argument
sources: { ... },
// sinkProxies to subscribe to our Cycle program's `main` function sinks
sinkProxies: { ... }
}
You shouldn't be working with that directly but its important that something needs to provide the top-level __INLINE_CODE_112__ and __INLINE_CODE_113__ in the first place. This is what __INLINE_CODE_114__ option of cycle connect for.
Also, the __INLINE_CODE_115__ed components' programs (__INLINE_CODE_116__ functions) are lazily attached to already running upper-level Cycle.js program, so the __INLINE_CODE_117__ program has to be __INLINE_CODE_118__.
So, root __INLINE_CODE_119__, aside from __INLINE_CODE_120__ option, also needs __INLINE_CODE_121__ defined, so that it knows how to run your top-level Cycle.js program. This would normally be just a __INLINE_CODE_122__ function from __INLINE_CODE_123__ package (or __INLINE_CODE_124__, or __INLINE_CODE_125__, depending on the reactive streams library in use).
You could just __INLINE_CODE_126__ your top-level __INLINE_CODE_127__ component like we did in the first example:
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
...
function main(sources) {
...
}
function App() {
return (
// Some JSX
...
);
}
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
Though this will work, its advisable to create a specialized __INLINE_CODE_128__ component just for this purpose:
// RootCycle.js
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
function main(sources) {
...
}
export const RootCycle = cycleConnect(main, {
root: true,
runFn: run
})(); // No component to wrap? Its okay, read ahead
export default RootCycle;
The top-level Cycle sources are actually __INLINE_CODE_129__ and __INLINE_CODE_130__, so root Cycle is also in charge of providing these __INLINE_CODE_131__ and __INLINE_CODE_132__ to any Cycle.js program down the tree, as __INLINE_CODE_133__.
__INLINE_CODE_134__ option is the object with keys defining properties of the __INLINE_CODE_135__ object, identical to how you'd do that in a regular Cycle.js program.
__INLINE_CODE_136__ option accepts an array of wrappers (order might matter there, so its a user-land option).
Also, the __INLINE_CODE_137__ function is optional (because you might need a __INLINE_CODE_138__ to be a __INLINE_CODE_139__ or for __INLINE_CODE_140__ only), so you can skip it, unless you have some logic to implement there.
Note that the __INLINE_CODE_141__ can be passed to any __INLINE_CODE_142__, even non-root, but __INLINE_CODE_143__ option is only valid for __INLINE_CODE_144__ Cycle and is ignored otherwise.
// RootCycle.js
import { run } from '@cycle/run';
import { makeHTTPDriver } from '@cycle/http';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const drivers = { HTTP: makeHTTPDriver() };
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
export default RootCycle;
__INLINE_CODE_145__ component is basically in charge of bootstrapping the "Cycle" part of your app here.
Then just wrap your __INLINE_CODE_146__ with it and e.g., render with __INLINE_CODE_147__:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import RootCycle from './RootCycle';
ReactDOM.render(
(
<RootCycle>
<App />
</RootCycle>
),
document.getElementById('root')
);
__INLINE_CODE_148__ option
You might have noticed in axamples above that we're not providing any component to a __INLINE_CODE_149__ HOC, leaving empty braces instead:
const ConnectedComponent = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
Well, that is not a mistake - the wrapped component is optional. When a "component to wrap" is provided to __INLINE_CODE_150__, then if we render the connected component, the __INLINE_CODE_151__ will also render __INLINE_CODE_152__ internally.
Thats all good and works (and what you'll be using most of the time anyway), but sometimes you just want to keep your React Element tree a bit cleaner when the wrapped component would be a kind of noop anyway (just forwarding __INLINE_CODE_153__ and nothing else), or component is some technical/lower-level like __INLINE_CODE_154__, or have some special rendering behavior.
So, we want to avoid seeing this in React Developer Tools:
<cycleConnect(SomeComponent)>
<SomeComponent>
...
</SomeComponent>
</cycleConnect(SomeComponent)>
You can use __INLINE_CODE_155__ option for that. It accepts the function with this signature:
(props) => ReactNode | null
If no component is provided for wrapping, nor __INLINE_CODE_156__ option is defined, the __INLINE_CODE_157__ option defaults to __INLINE_CODE_158__ function, so its either forwarding __INLINE_CODE_159__ if any or renders nothing.
If both __INLINE_CODE_160__ option is provided and component-to-wrap is passed, then __INLINE_CODE_161__ takes the precedence.
Note: Why not use the __INLINE_CODE_162__ option in place of wrapped component all the time? Well, its technically possible, espeically if your React component is a stateless functional component anyway.
But regular HOC wrapping is just common technique in React world and is nicely composable when you have other HOCs than __INLINE_CODE_163__.
And is cleaner too - you might be prototyping your app as a set of __INLINE_CODE_164__ components first, without even involving __INLINE_CODE_165__, then "connect" them all at once, or introduce __INLINE_CODE_166__ incrementally. Whatever :)
__INLINE_CODE_167__ option
Your component's __INLINE_CODE_168__ is by default automatically assigned to the following (depending on whether the wrapped component is provided, or __INLINE_CODE_169__ option, or neither:
- __INLINE_CODE_170__ when WrappedComponent is passed
- __INLINE_CODE_171__ when __INLINE_CODE_172__ prop is defined (and is truthy)
- __INLINE_CODE_173__ otherwise
You can override that like with any regular React component:
const ConnectedComponent = cycleConnect({
...
)(Component);
ConnectedComponent.displayName = 'ConnectedComponent';
But there's also a __INLINE_CODE_174__ option to set the generated (connected) component __INLINE_CODE_175__, which looks a bit cleaner:
const ConnectedComponent = cycleConnect({
...
displayName: 'ConnectedComponent'
)(Component);
Inside the Cycle
__INLINE_CODE_176__ really assumes that you know how to use Cycle.js. Its really a very simple yet very elegant pattern, so make sure to familiarize yourself with it.
Quick recap on what Cycle.js __INLINE_CODE_177__ function is. Its basically a pure function with the signature:
main :: (Sources) => Sinks
It accepts __INLINE_CODE_178__ as an argument and returns __INLINE_CODE_179__ and describes a dataflow using composition of reactive streams and their transformations with pure functions.
Please note that the __INLINE_CODE_180__ function is only run once per __INLINE_CODE_181__ component lifecycle. It doesn't run again on rerendering or something. This is because the actual call to __INLINE_CODE_182__ is to just build up a dataflow, not to get some rendering data.
The data flows through reactive streams, from __INLINE_CODE_183__ to __INLINE_CODE_184__ of the __INLINE_CODE_185__, until its __INLINE_CODE_186__ are unsubscribed from parent Cycle program's __INLINE_CODE_187__ (this happens when React component is unmounted).
Drivers
The __INLINE_CODE_188__ is supposed to run side effects for a Cycle.js program. Its outside the program itself, supposed to be impure and connects your Cycle app with the "outer world". This is an essential concept and is described in a great detail in Cycle.js docs on drivers.
There are no differences from idiomatic Cycle.js in how __INLINE_CODE_189__ handles __INLINE_CODE_190__ - it just hands it over to __INLINE_CODE_191__ directly. There is only one __INLINE_CODE_192__ driver (an __INLINE_CODE_193__ driver) that is always added internally by the __INLINE_CODE_194__ed component - it allows to invoke functions outside the __INLINE_CODE_195__ that is to be maintained pure.
Wrappers
The __INLINE_CODE_196__ is a rather simple thing - its just a function that takes __INLINE_CODE_197__ function that wraps it with the other __INLINE_CODE_198__ function that might in turn add some logic in between:
function someWrapper(originalMain) {
return wrappedMain(sources) {
// Can do something here, like transforming `sources` somehow
// ...
// Invoke the original `main` internally to obtain its sinks
const sinks = originalMain(sources);
// Can do something to `originalMain`s sinks here
// ...
return {
...sinks,
someOtherSink: xs.of({ ... }) // Can add a new sink
};
}
}
It is important that the wrapping __INLINE_CODE_199__ is maintained pure, so that your entire program is pure. If you need some effectful logic to run in a wrapping __INLINE_CODE_200__, you should consider using driver for that. Wrappers, just like any regular __INLINE_CODE_201__ function, can internally use __INLINE_CODE_202__ that are created from __INLINE_CODE_203__.
Wrappers can also add arbitrary __INLINE_CODE_204__ and could expect certain __INLINE_CODE_205__ to be returned from the Cycle program, depending on particular wrapper implementation.
Wrappers are usually created to encapsulate some non-business-logic stuff, you'd be normally describe anything related to the app itself in the "original" __INLINE_CODE_206__ function.
Most of __INLINE_CODE_207__ (aside from __INLINE_CODE_208__ HOC itself) is implemented with wrappers, that are created for each __INLINE_CODE_209__ed component automatically:
- __INLINE_CODE_210__ (to mess around with React component props)
- __INLINE_CODE_211__ (a gateway to React component lifecycle streams)
- __INLINE_CODE_212__ (to route the view events back from your wrapped React component aka view, and pass the events upstream, to parent components)
Sources
This is a Cycle.js program's input. __INLINE_CODE_213__ function shouldn't care whether something on the __INLINE_CODE_214__ is a __INLINE_CODE_215__ or some custom thing provided by one of the wrappers.
For __INLINE_CODE_216__ connected component, the __INLINE_CODE_217__ are __INLINE_CODE_218__ directly plus whatever __INLINE_CODE_219__ add to them.
Sinks
This is a Cycle.js program's output.
__INLINE_CODE_220__ invokes your __INLINE_CODE_221__ed component's __INLINE_CODE_222__ function, takes its __INLINE_CODE_223__ and subscribes the __INLINE_CODE_224__ passed from the parent component to these __INLINE_CODE_225__. Its how __INLINE_CODE_226__ is "attaching" the new component to a tree of Cycle programs.
If there's no parent component (thus, no __INLINE_CODE_227__) and your component is a "Root Cycle", then the sinks are handled to __INLINE_CODE_228__ to "run" as with a regular Cycle.js app.
React __INLINE_CODE_229__
The __INLINE_CODE_230__ed __INLINE_CODE_231__ component is essentially a view for the associated Cycle.js __INLINE_CODE_232__ function. And __INLINE_CODE_233__ encourages the view to be a "dumb" stateless function that takes __INLINE_CODE_234__ as an argument ad returns something of __INLINE_CODE_235__ type (i.e., __INLINE_CODE_236__s, __INLINE_CODE_237__, etc, whatever __INLINE_CODE_238__ component renders).
Note: The wrapped __INLINE_CODE_239__ shouldn't necessarily be a stateless functional component (SFC), you could have any __INLINE_CODE_240__ component wrapped, but its advisable to use functions for the most part as the whole point of __INLINE_CODE_241__ is to use __INLINE_CODE_242__ for anything that is not a view.
So, the __INLINE_CODE_243__ is the only communication channel from our Cycle.js part (the connected component's __INLINE_CODE_244__ function) to connected __INLINE_CODE_245__ component itself. We therefore need to pass the props from our Cycle program to the view (a __INLINE_CODE_246__ component) somehow.
This is where __INLINE_CODE_247__ comes into play and provides __INLINE_CODE_248__ source and sink for a __INLINE_CODE_249__ function. Its local to a component, so no other components will share the same __INLINE_CODE_250__ source or sink.
__INLINE_CODE_251__ sink
The __INLINE_CODE_252__ sink on __INLINE_CODE_253__ed is there to pass the props to the view:
function main(sources) {
return {
props: xs.of({ greetable: 'World' })
};
}
// Use the passed `greetable` prop
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
Remember, that the value you map to __INLINE_CODE_254__ sink should be an object of new props and that updated props will be merged with existing component __INLINE_CODE_255__ like:
const updatedProps = Object.assign({}, prevProps, newProps);
This means that you can easily overwrite any props you want and leave the others intact.
Also, note above that we're passing the __INLINE_CODE_256__ through an __INLINE_CODE_257__ stream and that might be synchronous (like in our example with a stream of static data that is sent immediately) but could also be asynchronous. In that case the value of some prop might be not yet available on the first render:
function main(sources) {
return {
props: xs.of({ greetable: 'World' }).compose(delay(3000))
};
}
// Trying to use the passed `greetable` prop
// But whooops, no `greetable` yet :(
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
So either provide a default value for that async prop's __INLINE_CODE_258__ or perform additional checks in the view, when rendering.
Note: When messing with the __INLINE_CODE_259__ in your __INLINE_CODE_260__, remember that any props that are passed from outside as an input to a __INLINE_CODE_261__ component, are also there (including callback functions, if there are any) and are also merged upon updates from outside (on __INLINE_CODE_262__).
Lets summarize: the __INLINE_CODE_263__ from the __INLINE_CODE_264__ sink are passed from the __INLINE_CODE_265__ function to the view (__INLINE_CODE_266__ component).
__INLINE_CODE_267__ source
Not only you'll need to provide the props to the view for rendering, but oftentimes you'd need your __INLINE_CODE_268__ to take part in transformations/calculations in the __INLINE_CODE_269__ function itself. For that reason, __INLINE_CODE_270__ provides the __INLINE_CODE_271__ source as well.
It allows you to have an __INLINE_CODE_272__ stream of your component's input __INLINE_CODE_273__. Note that the __INLINE_CODE_274__ is of type __INLINE_CODE_275__ and to take the actual stream of props, use its __INLINE_CODE_276__ attribute:
function main(sources) {
const name$ = sources.props.props$.map(props => props.name);
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
function MyComponent(props) {
return (
<div>
Hello, {props.name}! Your father is Mr. {props.father}.
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
...
// Somewhere else:
<MyConnectedComponent name="Luke Skywalker" father="Darth Vader" />
// renders:
//
// <div>
// Hello, LUKE SKYWALKER! Your father is Mr. Darth Vader.
// </div>
The new value on __INLINE_CODE_277__ is emitted every time __INLINE_CODE_278__ change. This could be overkill for the most cases, as you would only need just few props for your dataflow.
This is what __INLINE_CODE_279__ method for, it accepts the name of the nested prop (will return __INLINE_CODE_280__ if the stream value is not an object) and returns a stream of that prop only that is only got new value once this particular prop changes:
function main(sources) {
const name$ = sources.props.pluck('name');
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
The __INLINE_CODE_281__ed stream also performs shallow comparison and does __INLINE_CODE_282__ behind the scenes, so identical values don't cause a new value emitted on an __INLINE_CODE_283__.
Note that you don't need to always pull all props from your __INLINE_CODE_284__ function's __INLINE_CODE_285__ just for the sake of it to just forward to the __INLINE_CODE_286__ sink directly, you better only pick those you need in a dataflow with __INLINE_CODE_287__. The __INLINE_CODE_288__ passed from outside will be available on the view normally. You can overwrite them from inside your __INLINE_CODE_289__ though.
There's also __INLINE_CODE_290__ method that also accepts a nested prop name. The difference is that the result is not an __INLINE_CODE_291__ stream of prop values but the __INLINE_CODE_292__ instance itself that is scoped to that piece of __INLINE_CODE_293__:
function main(sources) {
const name$ = sources.props.pluck('name'); // Stream of strings
const nameSource = sources.props.select('someNestedObject'); // Scoped ReactPropsSource
const someNestedObjProperty$ = nameSource.pluck('someNestedObjectProperty');
...
}
This is useful when you want to __INLINE_CODE_294__ a stream of some sub-prop from deeper nested objects.
Note: Taking deep property stream this way isn't really nice currently as requires quite a bit of boilerplate - this part of API is already being thought out further and reconsidered.
Interactions
So far so good, but rendering the __INLINE_CODE_295__ isn't the only exciting thing we want to do with __INLINE_CODE_296__, right? We also want to handle events coming from the view(e.g., the DOM).
The way __INLINE_CODE_297__ handles events and nested components' callbacks is the biggest deviation from an idiomatic Cycle.js app using __INLINE_CODE_298__ driver. It refrains from a holistic "no handlers in the view" approach for a few reasons:
- We need something more in line with __INLINE_CODE_299__ as we're using React here
- We're not always handling DOM events but also need to handle some children components data/events passed back. We can't pass streams like in regular Cycle.js app, in __INLINE_CODE_300__ we do it via callbacks that are provided amongst children components' __INLINE_CODE_301__ and we need __INLINE_CODE_302__ed components to be compatible __INLINE_CODE_303__ components at all levels.
This is what "interactions" feature of __INLINE_CODE_304__ is for. It provides the unified interface for parent-child components communication and event handling that is aligned with a regular event/callback handling model on __INLINE_CODE_305__ side but also allows to access those events in form of __INLINE_CODE_306__ streams inside the __INLINE_CODE_307__ function.
Events triggering by the view
Every __INLINE_CODE_308__ component has some additional __INLINE_CODE_309__-specific props injected, so they are available on your view.
These are __INLINE_CODE_310__ which is a function that takes the __INLINE_CODE_311__ (a string) and optional __INLINE_CODE_312__ (of any type) and returns a function that will be a callback for your event or some child component prop function.
So, the signature is:
(interactionType, predefinedValue?) => (value) => void
Its supposed to be called imperatively in your view and is available as __INLINE_CODE_313__:
function MyComponent({ interact }) {
return (
<div>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
For the sake of being cleaner (and for better TypeScript support), __INLINE_CODE_314__ also leverages the ES2015 __INLINE_CODE_315__ feature to provide the object with dynamic properties, each representing an __INLINE_CODE_316__ with __INLINE_CODE_317__ dynamically defined after the function property name. Its available on __INLINE_CODE_318__ so we can rewrite the above snippet as:
function MyComponent({ interactions }) {
return (
<div>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
In the example above, the __INLINE_CODE_319__ function receives the click __INLINE_CODE_320__ as an argument and that is what you'll get in your __INLINE_CODE_321__ (next section).
While cleaner, it doesn't allow for the __INLINE_CODE_322__ to be set, so if you need some particular value to be emitted by the associated stream in your __INLINE_CODE_323__, you can either use __INLINE_CODE_324__ or create an inline function:
// Assuming you have `id` available, e.g.
// when looping/mapping over the collection of items
<button
type="button"
className="delete-btn"
onClick={interact('delete', id)}
>
Click me!
</button>
// OR
<button
type="button"
className="delete-btn"
onClick={() => interactions.delete(id)}
>
Click me!
</button>
View events handling with __INLINE_CODE_325__ source
There's internal __INLINE_CODE_326__ in __INLINE_CODE_327__ and it provides the __INLINE_CODE_328__ source for our __INLINE_CODE_329__ function. The __INLINE_CODE_330__ source is of type __INLINE_CODE_331__, which has the __INLINE_CODE_332__ method, which we can use to obtain interaction streams:
function main(sources) {
const buttonClick$ = sources.interactions.select('buttonClick');
// Do something with a buttonClick$, e.g.
// map it to the click coordinates props
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interact, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
The streams are __INLINE_CODE_333__ed by the same __INLINE_CODE_334__ string you provided to the __INLINE_CODE_335__ function on your view.
Similar to how __INLINE_CODE_336__ object with dynamic function-props is available on your view, __INLINE_CODE_337__ is a ES2015 __INLINE_CODE_338__ and it maintains the dynamic streams right on itself (just don't use __INLINE_CODE_339__ as its reserved method name here here):
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interactions, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
Note: If you try to get the non-existing stream, it will still work, this thing is dynamic, so the stream will be created nevertheless but will just never emit values. Be careful and look out for typos! Using TypeScript is highly recommended to avoid typos here.
Upstream interactions with __INLINE_CODE_340__ sink (calling callbacks on __INLINE_CODE_341__).
Now that we can handle the streams of view events and values from the children components, we would also need to somehow call the callbacks that are passed among our __INLINE_CODE_342__.
First, nothing prevents from following the common React way and call those directly from the view:
function MyComponent({ onButtonClick }) {
return (
<button type="button" onClick={onButtonClick}>
Click me!
</button>
);
}
// Somewhere else:
doSomething = (event) => { console.log('clicked!'); };
...
<MyComponent onButtonClick={doSomething} />
This is fine if you don't need that value's stream in your dataflow. This approach bypasses your Cycle.js program's __INLINE_CODE_343__ completely and doesn't make it impure by otherwise possibly passing an effectful function as __INLINE_CODE_344__. There's even no need to __INLINE_CODE_345__ your component in the above example.
Keep in mind that if __INLINE_CODE_346__ is optional there, you will also need to check whether that is defined before providing that as a callback (use __INLINE_CODE_347__ or TypeScript to mark it as either required or optional).
Oftentimes you will need to call some callback that lives on the __INLINE_CODE_348__ though, and this is what __INLINE_CODE_349__ sink provided by __INLINE_CODE_350__ does:
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
const coords$ = buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
);
// We both update the `props` (to render it in this component)
// and pass upstream
return {
props: coords$,
interactions: xs.of({
onCoordsChange: coords$
})
};
}
function MyComponent({ interactions }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
handleCoordsChange = (coords) => {
console.log('New coordinates received: ', coords);
};
...
<ConnectedComponent onCoordsChange={handleCoordsChange} />
// The new coords will be rendered by the component
// and the console will show the "New coordinates received"
// with new coords too.
Once the appropriate stream emits a value, that callback is called. It will also check if the callback was passed in the first place to skip the call attempt if no callback with a given name is passed among the __INLINE_CODE_351__.
Important: Note that __INLINE_CODE_352__ sink expects a stream of object whose property names correspond to callback names on __INLINE_CODE_353__ and property values are themselves streams too - whatever values those streams emit, will be passed as an argument to the callback.
So far so good, but we know that our __INLINE_CODE_354__ is a wrapper and not a driver, so what if one passed the effectful function as a callback, e.g., __INLINE_CODE_355__ passed as __INLINE_CODE_356__ above does a __INLINE_CODE_357__ internally - that is a side effect, right?
Well, this is why the function is not invoked directly, or inside the wrapper (since the wrapper is the part of the pure land of our __INLINE_CODE_358__s), its using the __INLINE_CODE_359__ driver internally. More on this below in a section about Side-effect control, just keep in mind that __INLINE_CODE_360__ and __INLINE_CODE_361__ should be pure and __INLINE_CODE_362__ follows that requirement.
React component lifecycle streams
You're probably not going to need __INLINE_CODE_363__ component lifecycle hooks often inside your __INLINE_CODE_364__, but still - they are provided via special __INLINE_CODE_365__. It provides the __INLINE_CODE_366__ source that is available on each __INLINE_CODE_367__ed __INLINE_CODE_368__ sources.
The lifecycle events are provided as __INLINE_CODE_369__ streams and their names mostly mirror the React lifecycle methods, but shortened to remove some redundant __INLINE_CODE_370__ prefixes:
React lifecycle method
__INLINE_CODE_371__ source property
Stream value
__INLINE_CODE_372__
__INLINE_CODE_373__
__INLINE_CODE_374__
__INLINE_CODE_375__
__INLINE_CODE_376__
__INLINE_CODE_377__
__INLINE_CODE_378__
__INLINE_CODE_379__
__INLINE_CODE_380__
__INLINE_CODE_381__
__INLINE_CODE_382__
__INLINE_CODE_383__
__INLINE_CODE_384__
__INLINE_CODE_385__
__INLINE_CODE_386__
__INLINE_CODE_387__
__INLINE_CODE_388__
__INLINE_CODE_389__
__INLINE_CODE_390__
__INLINE_CODE_391__
Error data
The "error data" on the __INLINE_CODE_392__ is an object of this shape:
{
error: Error;
errorInfo: ErrorInfo;
}
These are basically React __INLINE_CODE_393__ arguments squashed into a single object.
Also, note that __INLINE_CODE_394__ hook is not present there, since that is not really an event and affects how your component is rendered. __INLINE_CODE_395__ would be providing its own custom internal __INLINE_CODE_396__ instead for optimisation purposes and you could implement it on your wrapped component anyway.
Note: As well, no lifecycle events are passing anything related to React component __INLINE_CODE_397__, only taking __INLINE_CODE_398__ into account instead. This is because the __INLINE_CODE_399__ is meant to be managed differently when Cycle.js is in use. You can still use that for wrapped component but its out of play on the component generated by __INLINE_CODE_400__.
A quick example:
function main(sources) {
const willUnmount$ = { sources.lifecycle };
// Do something on unmounting, e.g. report that to a console
// via some hypothetical `logger` driver
return {
logger: willUnmount$.mapTo(`I'll be unmounted soon!`)
};
}
Side-effect control and __INLINE_CODE_401__ driver
Side-effect control is where Cycle.js really shines (not only implementation, it applies to the "cycle pattern" behind Cycle.js in the first place).
In order to keep the __INLINE_CODE_402__ part of __INLINE_CODE_403__ed component pure, we need to make sure that any potentially effectful function is not run inside our __INLINE_CODE_404__ directly.
This is what __INLINE_CODE_405__ driver is responsible for. It allows to only describe what function you want to call and with what arguments, then delegate that to a __INLINE_CODE_406__ which is supposed to run any impure functions outside of the app itself.
The way it works is very similar to that of __INLINE_CODE_407__ driver from __INLINE_CODE_408__ package where you don't actually run the request inside your __INLINE_CODE_409__ but just describe it and pass to __INLINE_CODE_410__ driver to handle. __INLINE_CODE_411__ driver is just a bit more generic and operates on functions.
__INLINE_CODE_412__ uses __INLINE_CODE_413__ driver internally (to run effects for upstream __INLINE_CODE_414__), but it is also available for you on any connected __INLINE_CODE_415__ as __INLINE_CODE_416__ sink automatically (i.e., without explicit configuration on a root cycle):
function log() {
console.log(`I'm effectful!`);
}
function saveToLocalStorage(message) {
localStorage.setItem('message', message);
}
function main(sources) {
const someCallbackFn$ = sources.props.pluck('someCallbackFunction');
return {
fnCallEffect: xs.merge(
xs.of({ fn: log }),
xs.of({ fn: saveToLocalStorage, args: ['some message to save'] }),
// Call the `props` callback function directly, this is how
// `interactions` sink works behind the scenes:
someCallbackFn$.map(fn => ({ fn }))
)
};
}
Isolation
Isolation is Cycle.js way of making the component's __INLINE_CODE_417__ and __INLINE_CODE_418__ isolated from influence of other components.
Make sure to carefully read more about Cycle.js isolation here. It might take a bit to get used to an idea.
To reiterate: the concept of isolation is important for Cycle.js apps. Its a little bit different for __INLINE_CODE_419__ because most things are scoped to a particular __INLINE_CODE_420__ed component already, like components have "local" __INLINE_CODE_421__, __INLINE_CODE_422__ and __INLINE_CODE_423__ sources and sinks and __INLINE_CODE_424__ components are also self-contained already.
There are though __INLINE_CODE_425__/__INLINE_CODE_426__ that will need the isolation feature, like __INLINE_CODE_427__ driver or __INLINE_CODE_428__ wrapper (a state management solution for Cycle.js apps). So, isolation makes sense for shared __INLINE_CODE_429__/__INLINE_CODE_430__, those you pass to __INLINE_CODE_431__ function (by defining the __INLINE_CODE_432__/__INLINE_CODE_433__ options on root __INLINE_CODE_434__) and that will be available across the entire tree of Cycle programs.
Note: The isolation works by scoping the source streams to particular component and a kind of unscoping of the sink streams before connecting to parent's sinks.
Also note, that each driver/wrapper decides on its own how its sources/sinks should be isolated.
In Cycle.js, you use the __INLINE_CODE_435__ function from __INLINE_CODE_436__ package directly, as you're just invoking the children __INLINE_CODE_437__ functions from your current component's __INLINE_CODE_438__ function:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// Do something with childSinks, combine somehow, etc
return {
...
};
}
__INLINE_CODE_439__ function has this simple signature:
isolate :: (MainFn, scope) => MainFn
It takes the __INLINE_CODE_440__ function and an optional __INLINE_CODE_441__ and returns an isolated __INLINE_CODE_442__ function.
__INLINE_CODE_443__ does also use the __INLINE_CODE_444__ package internally, but you can't call __INLINE_CODE_445__ directly on some __INLINE_CODE_446__ function. You'd never call one component's __INLINE_CODE_447__ from the other component's __INLINE_CODE_448__ directly with __INLINE_CODE_449__.
Naive explicit isolation (conceptually incorrect)
Just to illustrate the issue, there's an attempt to isolate the component's __INLINE_CODE_450__ sources, so that HTTP requests initiated from that components are scoped to it (and are unavailable to siblings).
Since we know that __INLINE_CODE_451__ returns a __INLINE_CODE_452__ function and __INLINE_CODE_453__ takes the __INLINE_CODE_454__ function as an argument, lets try to feed it an isolated version of it:
function main(sources) {
...
}
function MyComponent() {
...
}
const isolatedMain = isolate(main, { HTTP: 'myComponent' });
const ConnectedComponent = cycleConnect(isolatedMain)(MyComponent);
While this will won't raise any errors (because this is valid code after all), this approach brings a subtle issue.
This will isolate things for this particular components but not their children components, so in this case, children (connected childred __INLINE_CODE_455__ components) will have no __INLINE_CODE_456__ isolation scope for __INLINE_CODE_457__ channel.
This might seems like a no-issue, because hey, you can define different scopes for the children components right? Well, while this is technically possible, its conceptually incorrect and it will be completely broken in some cases, e.g., with __INLINE_CODE_458__ where the deeper you go - the "more filtered" source streams you get.
Important! The above note about it being "conceptually incorrect" refers to the way __INLINE_CODE_459__ maintains the tree of Cycle.js programs internally. It tries to make the entire app structure look the same way as you'd manually write a Cycle.js app. So, when you __INLINE_CODE_460__ at some level, then if inside your __INLINE_CODE_461__ you also call some __INLINE_CODE_462__ happens, then __INLINE_CODE_463__ gets isolated sources no matter what.
__INLINE_CODE_464__ option for __INLINE_CODE_465__
The first way to correctly isolate __INLINE_CODE_466__ed component is to use the __INLINE_CODE_467__ option for __INLINE_CODE_468__ itself, so the above can be rewritten as:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main, {
isolate: { HTTP: 'myComponent' }
})(MyComponent);
This will work perfectly, with all children __INLINE_CODE_469__ed components having __INLINE_CODE_470__ namespace applied to __INLINE_CODE_471__ sources/sinks in their __INLINE_CODE_472__ functions.
The __INLINE_CODE_473__ option accepts a __INLINE_CODE_474__ or the __INLINE_CODE_475__ and in that case its passed directly to __INLINE_CODE_476__ package's __INLINE_CODE_477__ as a __INLINE_CODE_478__ argument. So, check out the __INLINE_CODE_479__ function API reference.
__INLINE_CODE_480__ also makes it accept a function that takes component __INLINE_CODE_481__ as an argument and should return the scope of __INLINE_CODE_482__ or __INLINE_CODE_483__ type that is then also passed to __INLINE_CODE_484__ package's __INLINE_CODE_485__. This allows for some dynamic isolation scopes to be set based on the React component's initial props.
Important: Despite the __INLINE_CODE_486__ is provided as an option to __INLINE_CODE_487__ when defining the connected component, the actual isolation will only happen when component instance is created. This is why its possible to dynamically resolve the __INLINE_CODE_488__ based on the component __INLINE_CODE_489__ in __INLINE_CODE_490__.
__INLINE_CODE_491__ prop
Despite the __INLINE_CODE_492__ option will work nicely for some cases, there's a pattern in how it is utilized in pure Cycle.js programs. Lets take a look at pure Cycle.js example again:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// ...
return {
...
};
}
The important part is that in Cycle.js app its not a child who decides how to isolate itself but __INLINE_CODE_493__ is provided from the parent instead.
Most of the time, we want to maintain the same approach with __INLINE_CODE_494__. The advantage is that component in this case is totally agnostic from the provided scope, it doesn't even care whether its isolated or not at all - all it knows is that it expects certain __INLINE_CODE_495__ and returns certain __INLINE_CODE_496__. This makes the component reusable.
This is especially important with __INLINE_CODE_497__, where you want to control where your child components "attach" to the state.
In other words, the parent decides how to compose children components, not the other way around, because otherwise the child would need to have an intimate knowledge of something that really belongs to parent and thus not being reusable and breaking encapsulation.
For this reason, every __INLINE_CODE_498__ed React component accepts an __INLINE_CODE_499__ prop, so you can use that just like any other prop:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
// Isolate everything
<ConnectedComponent isolate="myComponent" />
// Isolate HTTP only
<ConnectedComponent isolate={{ HTTP: 'myComponent' }} />
<ConnectedComponent isolate={{ HTTP: 'myComponent_2' }} />
In contrast with __INLINE_CODE_500__ option, the __INLINE_CODE_501__ prop can only accept __INLINE_CODE_502__ or __INLINE_CODE_503__ that is directly passed to __INLINE_CODE_504__ function from __INLINE_CODE_505__, thus maintaining full parity with pure Cycle.js approach. Indeed, having a function that accepts initial __INLINE_CODE_506__ wouldn't make much sense because you're anyway providing props that could be different for different component instances when rendering that component.
__INLINE_CODE_507__ helper component
Since isolation in __INLINE_CODE_508__ applies to both "current" __INLINE_CODE_509__ed component and all its __INLINE_CODE_510__ed children, we might sometimes need to define some connected component to only be a common parent for some children, which must provide isolation but wouldn't contain any logic.
This is what a helper __INLINE_CODE_511__ component for. It accepts the __INLINE_CODE_512__ prop (that is directly passed as a __INLINE_CODE_513__ argument to __INLINE_CODE_514__ function of __INLINE_CODE_515__):
<Isolator scope={{ HTTP: 'someNamespace' }}>
<ConnectedComponent someProps="1" />
<ConnectedComponent someProps="2" />
<ConnectedComponent someProps="3" />
</Isolator>
This assists the isolation composition at view level.
Extra: State management with __INLINE_CODE_516__
__INLINE_CODE_517__ is the idiomatic Cycle.js fractal state management solution. It is tailored for Cycle.js apps but since __INLINE_CODE_518__ is using the real Cycle.js, it stays fully compatible here as well.
Using __INLINE_CODE_519__ with __INLINE_CODE_520__ is optional, since you might be using different lib (e.g., __INLINE_CODE_521__) for that.
To start, just read the onionify docs and install the package:
$ npm install --save cycle-onionify
and pass that with __INLINE_CODE_522__ array to a __INLINE_CODE_523__ connected component (e.g., Root Cycle):
// RootCycle.js
import { run } from '@cycle/run';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
wrappers
})();
export default RootCycle;
Now every __INLINE_CODE_524__ed component that is a child of the above-mentioned __INLINE_CODE_525__ in React component tree got the __INLINE_CODE_526__ source and sink.
Important: Notice that you're all set - its purely a Cycle.js land. __INLINE_CODE_527__ doesn't provide any additional functionality for __INLINE_CODE_528__. Instead, it provides few helpers for isolation and working with collections on the React side.
All the exportables related to __INLINE_CODE_529__ reside in the extra package: __INLINE_CODE_530__. It is installed along with the main lib but is not __INLINE_CODE_531__d/__INLINE_CODE_532__ed by default, so won't be bundled (by Webpack, Rollup, etc) unless you're using it.
This extra sub-package contains few things:
- __INLINE_CODE_533__ helper component
- __INLINE_CODE_534__ helper component
- __INLINE_CODE_535__ helper factory function
Read ahead for details on each.
In __INLINE_CODE_536__, the state/reducers layer composition is done via isolation, so the first thing here is __INLINE_CODE_537__:
__INLINE_CODE_538__ helper component
This is pretty much like __INLINE_CODE_539__ but is here to specifically isolate __INLINE_CODE_540__ sources/sinks only.
So, the:
import { StateIsolator } from 'react-cycle-connect/lib/extra/onionify';
// ...
<StateIsolator lens="someStateProperty">
<NestedComponent />
</StateIsolator>
is somewhat a convenient shortcut to:
import { Isolator } from 'react-cycle-connect';
// ...
<Isolator scope={{ onion: 'someStateProperty', '*': null }}>
<NestedComponent />
</Isolator>
Notice how __INLINE_CODE_541__'s prop is explicitly named __INLINE_CODE_542__. It just implies that oftentimes you'll be using "lens" to isolate the state. Consider a string just a shortcut to a more elaborate __INLINE_CODE_543__ object.
In the example above, the __INLINE_CODE_544__'s __INLINE_CODE_545__ function __INLINE_CODE_546__ will represent only that "someStateProperty" piece of state. Pretty much how you'd __INLINE_CODE_547__ the __INLINE_CODE_548__ channel in an idiomatic Cycle.js app.
There's also a __INLINE_CODE_549__ property, so that you can change the name of the source/sink inside your __INLINE_CODE_550__ to something else. Thats to be on par with __INLINE_CODE_551__ features, where you can also change that name.
Handling collection state manually
Managing collections of components in pure Cycle.js with __INLINE_CODE_552__ is a kind of a recognized challenge. This is why __INLINE_CODE_553__ ended up providing __INLINE_CODE_554__ helper function - see their docs for more detail.
While __INLINE_CODE_555__ also has a __INLINE_CODE_556__ helper component that is very similar to __INLINE_CODE_557__ __INLINE_CODE_558__ function, lets first try to pretend we don't have it and imagine how'd we handle the state without it.
Note: Nothing prevents you from skipping right to a description of how __INLINE_CODE_559__ works, but it might be useful to understand the rationale behind it, so reading this section is recommended.
Handling collections is different with React than in Cycle.js, e.g., you can always just render React components in a loop or __INLINE_CODE_560__ an array of data to an array of components and React will render that.
When you want to render some __INLINE_CODE_561__ data from your state, its easy without any helpers:
:
{
list: [
{ id: 1, name: 'Obi-Wan Kenobi', kills: 100 },
{ id: 2, name: 'Luke Skywalker', kills: 25 },
{ id: 3, name: 'Darth Vader', kills: 9000 }
]
}
*/
// ListItem.jsx
function ListItem({ itemData }) {
return (
<li>
{itemData.name} (ID: {itemData.id}, kills: {itemData.kills})
</li>
);
}
// List.jsx
function main(sources) {
// Just pass as `props` directly
return {
props: sources.onion.state$
};
}
function List({ list }) {
return (
<ul>
{list.map(item => (
<ListItem key={item.id} itemData={item} />
))}
</ul>
);
}
const ConnectedList = cycleConnect(main)(List);
This keeps your __INLINE_CODE_562__ component just a "dumb" view, there's no even need of connecting it to the Cycle tree. So far so good.
Then you want to add more item-level capabilities, like editing an item (incrementing hero __INLINE_CODE_563__ in our case, for simplicity) or deleting an item. You can still manage the state in the __INLINE_CODE_564__ component, act upon some callbacks for particular item and only stream reducers from inside __INLINE_CODE_565__ component's __INLINE_CODE_566__:
:
{
list: [
{ id: 1, name: 'Obi-Wan Kenobi', kills: 100 },
{ id: 2, name: 'Luke Skywalker', kills: 25 },
{ id: 3, name: 'Darth Vader', kills: 9000 }
]
}
*/
// ListItem.jsx
function ListItem({ itemData, onIncrementClick, onDeleteClick }) {
return (
<li>
{itemData.name} (ID: {itemData.id}, kills: {itemData.kills}) -
<button type="button" onClick={onIncrementClick}>
Increment "kills"
</button>
<button type="button" onClick={onDeleteClick}>
Delete
</button>
</li>
);
}
// List.jsx
function intent(interactions) {
return {
itemIncrement$: interactions.itemIncrementClick,
itemDelete$: interactions.itemDeleteClick
};
}
function model(actions) {
const defaultReducer$ = xs.of(function defaultReducer(state) {
if (typeof state !== 'undefined') return state;
return {
list: []
};
});
const incrementItemReducer$ = actions.itemIncrement$.map(
(id) => function incrementItemReducer(state) {
return {
...state,
list: state.list.map(listItem => {
if (listItem.id !== id) return listItem;
return {
...listItem,
kills: listItem.kills + 1
};
})
};
}
);
const deleteItemReducer$ = actions.itemDelete$.map(
(id) => function deleteItemReducer(state) {
return {
...state,
list: state.list.filter(listItem => listItem.id !== id)
};
}
);
return xs.merge(
defaultReducer$,
incrementItemReducer$,
deleteItemReducer$
);
}
function main(sources) {
const actions = intent(sources.interactions);
const reducer$ = model(actions);
return {
onion: reducer$,
props: sources.onion.state$
};
}
function List({ interactions, list }) {
return (
<ul>
{list.map(item => (
<ListItem
key={item.id}
itemData={item}
onIncrementClick={() => interactions.itemIncrementClick(item.id)}
onDeleteClick={() => interactions.itemDeleteClick(item.id)}
/>
))}
</ul>
);
}
const ConnectedList = cycleConnect(main)(List);
There is already some noise in reducers, especially on updates. Also, what if there's another piece of state sibling to __INLINE_CODE_567__ property? __INLINE_CODE_568__ gets overburdened quickly.
You'd also quickly realize that this makes the collection item state inherently bound to __INLINE_CODE_569__, so while you could be reusing __INLINE_CODE_570__ somewhere else, you couldn't reuse its state management part.
This is unfortunate because certain actions ("edit" and "delete") are related to a single item and can be managed at that item's level. This is totally possible with __INLINE_CODE_571__ because its a fractal state management thing, so we could be taking advantage of that fractal aspect with __INLINE_CODE_572__ too.
So, we'd better be managing internal item state at the __INLINE_CODE_573__ component level. To do that, let's __INLINE_CODE_574__ the __INLINE_CODE_575__ component and make it handle its stuff. We need to carefully let each item's component know which piece of state does belong to it, and we'll use __INLINE_CODE_576__ for that:
:
{
list: [
{ id: 1, name: 'Obi-Wan Kenobi', kills: 100 },
{ id: 2, name: 'Luke Skywalker', kills: 25 },
{ id: 3, name: 'Darth Vader', kills: 9000 }
]
}
*/
// ListItem.jsx
function intent(interactions) {
return {
increment$: interactions.incrementClick,
delete$: interactions.deleteClick
};
}
function model(actions) {
// Note: default reducer assumes that we always have a valid
// state from the parent component. It just makes it undefined
// explicitly otherwise.
const defaultReducer$ = xs.of(function defaultReducer(state) {
if (state) return state;
return void 0;
});
const incrementReducer$ = actions.increment$.map(
() => function incrementReducer(state) {
return {
...state,
kills: state.kills + 1
};
}
);
const deleteReducer$ = actions.delete$.map(
() => function deleteReducer(state) {
return void 0;
}
);
return xs.merge(
defaultReducer$,
incrementReducer$,
deleteReducer$
);
}
function main(sources) {
const actions = intent(sources.interactions);
const reducer$ = model(actions);
/*
The initial `stateFork
- updated to more recent version of cycle, including @cycle/state
- updated to React 16.9 api, including new lifecycles and context api
- merged onionify separate lib so import is __INLINE_CODE_0__
- added tests
- published at @iilab/react-cycle-connect
react-cycle-connect
An experimental library that allows using Cycle.js as a reactive dataflow framework for React.
Written in TypeScript.
This little thing allows to define your app's view layer using regular React components, but implement the actual logic behind it using pure functional reactive approach, taking advantage of Cycle.js' Model-View-Intent pattern and its exceptional side effect control.
This isn't a custom implementation of Cycle pattern for __INLINE_CODE_1__ but a link to real Cycle.js instead, so any Cycle.js drivers and wrappers can be applied (e.g., __INLINE_CODE_2__ for state management). Also, for this reason, some knowledge about how __INLINE_CODE_3__ works is assumed.
Note: This is an experimental lib and currently works with __INLINE_CODE_4__ and __INLINE_CODE_5__ only. If all goes good, the support for __INLINE_CODE_6__/__INLINE_CODE_7__ (instead of __INLINE_CODE_8__) and for __INLINE_CODE_9__/__INLINE_CODE_10__ (instead of __INLINE_CODE_11__) is anticipated. Nothing prevents from having __INLINE_CODE_12__ covered too.
UPDATE (MAR 2018): Some internals overhaul is pending and it's currently waiting for Cycle.js to settle on hybrid push-pull reactivity and have internals rewritten to use Callbags instead of __INLINE_CODE_13__. So, __INLINE_CODE_14__ is going to be using Callbags by default too.
Any feedback is highly appreciated!
Table of Contents
- react-cycle-connect
- Table of Contents
- Shup up and show me the code
- Installation
- Motivation
- How it works (architecture)
- Guide
- API reference
- Alternatives / Competitors
- FAQ
- Roadmap
Shup up and show me the code
Cycle.js's counter example ported over:
import React from 'react';
import ReactDOM from 'react-dom';
import xs from 'xstream';
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
// Your Cycle.js `main` function
function main(sources) {
const action$ = xs.merge(
sources.interactions.decrement.map(ev => -1),
sources.interactions.increment.map(ev => +1)
);
const count$ = action$.fold((acc, x) => acc + x, 0);
return {
props: count$.map(count => ({ count }))
};
}
// Your React component
function App({ interactions, count }) {
return (
<div>
<button onClick={interactions.decrement}>Decrement</button>
<button onClick={interactions.increment}>Increment</button>
<p>Counter: {count}</p>
</div>
);
}
// Some connection magic
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
// Render normally as it's a regular React app
ReactDOM.render(
<ConnectedApp />,
document.getElementById('root')
);
Also - there's official TodoMVC example with __INLINE_CODE_33__ for state management.
Installation
Using npm:
$ npm install --save react-cycle-connect
Using yarn:
$ yarn add react-cycle-connect
Motivation
The motivation to create this lib was based off the need to retain the React-style composability of the view layer using regular React components, reuse existing React components, make it (the lib) easily/incrementally adoptable in an ongoing React project, but also take advantage of the Cycle.js' MVI pattern and side effect control.
At the same time, while the most valuable thing from __INLINE_CODE_34__ is the pattern itself, there are numerous available libs/plugins for Cycle.js to take advantage of, letting us take the best of both worlds.
This way, you can still use anything from React stack (e.g., __INLINE_CODE_35__ to compose your routed app screens), but describe the interactions as a dataflow, with pure functions and reactive streams. Most of React components are going to be "dumb" (and also stateless functional aka SFC) since all the backing logic and cross-component communication would be handled by a tree of __INLINE_CODE_36__ programs.
Why not just use Cycle.js alone?
That is what I'd recommend considering in the first place. Its fast, its cool, the __INLINE_CODE_37__ (virtual DOM rendering engine behind Cycle's DOM driver) is goddamn fast and reliable. If you don't intend to use __INLINE_CODE_38__ components in any way, don't even get bothered. The __INLINE_CODE_39__ is more about "introducing Cycle.js to __INLINE_CODE_40__", not the other way around.
Is it a "Cycle.js in React" or "React in Cycle.js"?
More of a "Cycle.js in React". The app is bootstrapped by rendering a React component to the DOM, view is composed the React way. The root Cycle.js program is being __INLINE_CODE_41__ by rendering a root __INLINE_CODE_42__ed component. Though, it doesn't prescribe how you write your Cycle.js programs, and aside from the DOM generation part, its just Cycle.js all the way.
How it works (architecture)
Every __INLINE_CODE_43__ed component got a real Cycle.js program "attached" to it. The boundary between the two is __INLINE_CODE_44__. From your Cycle.js program standpoint, __INLINE_CODE_45__ is a view. Cycle.js program can update __INLINE_CODE_46__ that React component will then render.
A bunch of nested React components in your app represent a "tree of React components". __INLINE_CODE_47__ maintains a "tree of Cycle.js programs" that map to __INLINE_CODE_48__ed components and takes advantage of React's context feature to provide links between the nodes. Each connected component provides scoped links to the children nodes .
New "Cycle nodes" are attached lazily, upon associated React component initialization (this is where React controls how Cycle programs are run). The above-mentioned "link" is just an object on React __INLINE_CODE_49__ that brings couple properties:
- __INLINE_CODE_50__ for the child connected component's __INLINE_CODE_51__ function; and
- __INLINE_CODE_52__ to subscribe to that component's __INLINE_CODE_53__ function __INLINE_CODE_54__.
This is where something unusual for the Cycle.js happens - we subscribe to and unsubscribe from connected component's __INLINE_CODE_55__ function __INLINE_CODE_56__ based on React component's lifetime hooks. In Cycle.js, you normally just run the app at startup and let it run as an ongoing process. But this is where __INLINE_CODE_57__ performs the sync between the two worlds.
Overall, aside from using React __INLINE_CODE_58__ (which is just an implementation detail) and some intense wrapping on the __INLINE_CODE_59__ side to make laziness possible, __INLINE_CODE_60__ tries to represent the entire tree of Cycle.js programs as close as possible to what you'd end up with by writing idiomatic Cycle.js with explicit calling of nested component's __INLINE_CODE_61__ functions from the parent component __INLINE_CODE_62__ function. The result of children components is always __INLINE_CODE_63__d.
Aside from passing the __INLINE_CODE_64__ to children components, __INLINE_CODE_65__ also explicitly applies some internal wrappers to integrate with React component: __INLINE_CODE_66__, __INLINE_CODE_67__ and __INLINE_CODE_68__, that are scoped to a connected component's Cycle.js program only. It also explicitly runs the internal __INLINE_CODE_69__ driver for the __INLINE_CODE_70__ nodes by default - this is necessary for the __INLINE_CODE_71__ sink to work (because its supposed to run callbacks passed via __INLINE_CODE_72__ and that might be an effectful function).
Note that the entire internal "tree" of Cycle.js programs is maintained pure, including internal component-scoped wrappers, just as you would do with regular Cycle.js program.
Differences from idiomatic Cycle.js usage
Note: there's a difference in how'd you write the __INLINE_CODE_73__/__INLINE_CODE_74__ app vs pure __INLINE_CODE_75__ app. There's no misuse of Cycle.js per se, we just don't use it for rendering with __INLINE_CODE_76__ - and that applies certain implications to the approach.
__INLINE_CODE_77__ and __INLINE_CODE_78__, while both allow to create user interfaces, imply different approaches to the app architecture. The most important part here is that they are conceptually different when it comes to generating the VDOM.
In order to combine the two approaches nicely, there are certain deviations from how'd you write the __INLINE_CODE_79__-only app to allow for more optimal React DX.
The main difference from a pure __INLINE_CODE_80__ way is that its not a Cycle program that technically generates VDOM - instead, there's React component to represent a piece of UI. __INLINE_CODE_81__ approach to view/__INLINE_CODE_82__ event handling is rather purist - view only defines the markup and nothing else (i.e., user interaction/event handlers are defined inside Cycle program itself and not as some view component __INLINE_CODE_83__/etc attribute). That is not a crazy random opinion but instead a very well thought and weighted out decision by Cycle.js team. __INLINE_CODE_84__ takes a different approach, which isn't that strict and is more in line with React, with callbacks amongst __INLINE_CODE_85__ and all that stuff. It enables the events/component callbacks handling via interactions.
The view composition is obviously completely different. In __INLINE_CODE_86__ you run each component's __INLINE_CODE_87__ function that (possibly) generates a VDOM node and then explicitly combine the pieces of markup into a single VDOM stream that is then given to a DOM driver. With __INLINE_CODE_88__ you compose the view layer with React components normally. You can just connect those to Cycle.js program to "outsource" the actual logic to it.
Guide
This assumes the preliminary knowledge of both __INLINE_CODE_89__ and __INLINE_CODE_90__ (and __INLINE_CODE_91__). It doesn't provide much of the insight on how these work, but is instead focused on how __INLINE_CODE_92__ combines the two.
Basics
The result of __INLINE_CODE_93__ call is a HOC that optionally takes a React component (similar to that __INLINE_CODE_94__ function of __INLINE_CODE_95__) and returns a component that is augmented with backing Cycle.js program. This Cycle.js program is internally wrapped into a "Cycle Node" that, when initialized, is connected to a tree of Cycle.js programs. "Cycle Nodes" of every __INLINE_CODE_96__ed components are linked together via React __INLINE_CODE_97__.
Note, though, that there are no direct references from one component's Cycle program to another component's Cycle program, i.e., you're not invoking one __INLINE_CODE_98__ function from the other (this is perfectly possible though, but since we're not using __INLINE_CODE_99__ here, it would only handle non-DOM stuff). Instead, __INLINE_CODE_100__ handles the linking behind the scenes, so the "sibling" Cycle programs are fed with the same __INLINE_CODE_101__ and subscribe their __INLINE_CODE_102__ to the same __INLINE_CODE_103__.
There's essential concept of isolation in Cycle.js, so every __INLINE_CODE_104__ed component accepts an __INLINE_CODE_105__ prop, that is directly passed to __INLINE_CODE_106__ function from __INLINE_CODE_107__ package. Read more about Cycle.js isolation here.
Root Cycle
Every __INLINE_CODE_108__ed component provides a __INLINE_CODE_109__ object to its children through React __INLINE_CODE_110__ and also expects that __INLINE_CODE_111__ from some parent component (not necessarily immediate parent).
That link object has the following shape:
{
// Sources to provide to our Cycle.js program's `main` function as an argument
sources: { ... },
// sinkProxies to subscribe to our Cycle program's `main` function sinks
sinkProxies: { ... }
}
You shouldn't be working with that directly but its important that something needs to provide the top-level __INLINE_CODE_112__ and __INLINE_CODE_113__ in the first place. This is what __INLINE_CODE_114__ option of cycle connect for.
Also, the __INLINE_CODE_115__ed components' programs (__INLINE_CODE_116__ functions) are lazily attached to already running upper-level Cycle.js program, so the __INLINE_CODE_117__ program has to be __INLINE_CODE_118__.
So, root __INLINE_CODE_119__, aside from __INLINE_CODE_120__ option, also needs __INLINE_CODE_121__ defined, so that it knows how to run your top-level Cycle.js program. This would normally be just a __INLINE_CODE_122__ function from __INLINE_CODE_123__ package (or __INLINE_CODE_124__, or __INLINE_CODE_125__, depending on the reactive streams library in use).
You could just __INLINE_CODE_126__ your top-level __INLINE_CODE_127__ component like we did in the first example:
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
...
function main(sources) {
...
}
function App() {
return (
// Some JSX
...
);
}
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
Though this will work, its advisable to create a specialized __INLINE_CODE_128__ component just for this purpose:
// RootCycle.js
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
function main(sources) {
...
}
export const RootCycle = cycleConnect(main, {
root: true,
runFn: run
})(); // No component to wrap? Its okay, read ahead
export default RootCycle;
The top-level Cycle sources are actually __INLINE_CODE_129__ and __INLINE_CODE_130__, so root Cycle is also in charge of providing these __INLINE_CODE_131__ and __INLINE_CODE_132__ to any Cycle.js program down the tree, as __INLINE_CODE_133__.
__INLINE_CODE_134__ option is the object with keys defining properties of the __INLINE_CODE_135__ object, identical to how you'd do that in a regular Cycle.js program.
__INLINE_CODE_136__ option accepts an array of wrappers (order might matter there, so its a user-land option).
Also, the __INLINE_CODE_137__ function is optional (because you might need a __INLINE_CODE_138__ to be a __INLINE_CODE_139__ or for __INLINE_CODE_140__ only), so you can skip it, unless you have some logic to implement there.
Note that the __INLINE_CODE_141__ can be passed to any __INLINE_CODE_142__, even non-root, but __INLINE_CODE_143__ option is only valid for __INLINE_CODE_144__ Cycle and is ignored otherwise.
// RootCycle.js
import { run } from '@cycle/run';
import { makeHTTPDriver } from '@cycle/http';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const drivers = { HTTP: makeHTTPDriver() };
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
export default RootCycle;
__INLINE_CODE_145__ component is basically in charge of bootstrapping the "Cycle" part of your app here.
Then just wrap your __INLINE_CODE_146__ with it and e.g., render with __INLINE_CODE_147__:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import RootCycle from './RootCycle';
ReactDOM.render(
(
<RootCycle>
<App />
</RootCycle>
),
document.getElementById('root')
);
__INLINE_CODE_148__ option
You might have noticed in axamples above that we're not providing any component to a __INLINE_CODE_149__ HOC, leaving empty braces instead:
const ConnectedComponent = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
Well, that is not a mistake - the wrapped component is optional. When a "component to wrap" is provided to __INLINE_CODE_150__, then if we render the connected component, the __INLINE_CODE_151__ will also render __INLINE_CODE_152__ internally.
Thats all good and works (and what you'll be using most of the time anyway), but sometimes you just want to keep your React Element tree a bit cleaner when the wrapped component would be a kind of noop anyway (just forwarding __INLINE_CODE_153__ and nothing else), or component is some technical/lower-level like __INLINE_CODE_154__, or have some special rendering behavior.
So, we want to avoid seeing this in React Developer Tools:
<cycleConnect(SomeComponent)>
<SomeComponent>
...
</SomeComponent>
</cycleConnect(SomeComponent)>
You can use __INLINE_CODE_155__ option for that. It accepts the function with this signature:
(props) => ReactNode | null
If no component is provided for wrapping, nor __INLINE_CODE_156__ option is defined, the __INLINE_CODE_157__ option defaults to __INLINE_CODE_158__ function, so its either forwarding __INLINE_CODE_159__ if any or renders nothing.
If both __INLINE_CODE_160__ option is provided and component-to-wrap is passed, then __INLINE_CODE_161__ takes the precedence.
Note: Why not use the __INLINE_CODE_162__ option in place of wrapped component all the time? Well, its technically possible, espeically if your React component is a stateless functional component anyway.
But regular HOC wrapping is just common technique in React world and is nicely composable when you have other HOCs than __INLINE_CODE_163__.
And is cleaner too - you might be prototyping your app as a set of __INLINE_CODE_164__ components first, without even involving __INLINE_CODE_165__, then "connect" them all at once, or introduce __INLINE_CODE_166__ incrementally. Whatever :)
__INLINE_CODE_167__ option
Your component's __INLINE_CODE_168__ is by default automatically assigned to the following (depending on whether the wrapped component is provided, or __INLINE_CODE_169__ option, or neither:
- __INLINE_CODE_170__ when WrappedComponent is passed
- __INLINE_CODE_171__ when __INLINE_CODE_172__ prop is defined (and is truthy)
- __INLINE_CODE_173__ otherwise
You can override that like with any regular React component:
const ConnectedComponent = cycleConnect({
...
)(Component);
ConnectedComponent.displayName = 'ConnectedComponent';
But there's also a __INLINE_CODE_174__ option to set the generated (connected) component __INLINE_CODE_175__, which looks a bit cleaner:
const ConnectedComponent = cycleConnect({
...
displayName: 'ConnectedComponent'
)(Component);
Inside the Cycle
__INLINE_CODE_176__ really assumes that you know how to use Cycle.js. Its really a very simple yet very elegant pattern, so make sure to familiarize yourself with it.
Quick recap on what Cycle.js __INLINE_CODE_177__ function is. Its basically a pure function with the signature:
main :: (Sources) => Sinks
It accepts __INLINE_CODE_178__ as an argument and returns __INLINE_CODE_179__ and describes a dataflow using composition of reactive streams and their transformations with pure functions.
Please note that the __INLINE_CODE_180__ function is only run once per __INLINE_CODE_181__ component lifecycle. It doesn't run again on rerendering or something. This is because the actual call to __INLINE_CODE_182__ is to just build up a dataflow, not to get some rendering data.
The data flows through reactive streams, from __INLINE_CODE_183__ to __INLINE_CODE_184__ of the __INLINE_CODE_185__, until its __INLINE_CODE_186__ are unsubscribed from parent Cycle program's __INLINE_CODE_187__ (this happens when React component is unmounted).
Drivers
The __INLINE_CODE_188__ is supposed to run side effects for a Cycle.js program. Its outside the program itself, supposed to be impure and connects your Cycle app with the "outer world". This is an essential concept and is described in a great detail in Cycle.js docs on drivers.
There are no differences from idiomatic Cycle.js in how __INLINE_CODE_189__ handles __INLINE_CODE_190__ - it just hands it over to __INLINE_CODE_191__ directly. There is only one __INLINE_CODE_192__ driver (an __INLINE_CODE_193__ driver) that is always added internally by the __INLINE_CODE_194__ed component - it allows to invoke functions outside the __INLINE_CODE_195__ that is to be maintained pure.
Wrappers
The __INLINE_CODE_196__ is a rather simple thing - its just a function that takes __INLINE_CODE_197__ function that wraps it with the other __INLINE_CODE_198__ function that might in turn add some logic in between:
function someWrapper(originalMain) {
return wrappedMain(sources) {
// Can do something here, like transforming `sources` somehow
// ...
// Invoke the original `main` internally to obtain its sinks
const sinks = originalMain(sources);
// Can do something to `originalMain`s sinks here
// ...
return {
...sinks,
someOtherSink: xs.of({ ... }) // Can add a new sink
};
}
}
It is important that the wrapping __INLINE_CODE_199__ is maintained pure, so that your entire program is pure. If you need some effectful logic to run in a wrapping __INLINE_CODE_200__, you should consider using driver for that. Wrappers, just like any regular __INLINE_CODE_201__ function, can internally use __INLINE_CODE_202__ that are created from __INLINE_CODE_203__.
Wrappers can also add arbitrary __INLINE_CODE_204__ and could expect certain __INLINE_CODE_205__ to be returned from the Cycle program, depending on particular wrapper implementation.
Wrappers are usually created to encapsulate some non-business-logic stuff, you'd be normally describe anything related to the app itself in the "original" __INLINE_CODE_206__ function.
Most of __INLINE_CODE_207__ (aside from __INLINE_CODE_208__ HOC itself) is implemented with wrappers, that are created for each __INLINE_CODE_209__ed component automatically:
- __INLINE_CODE_210__ (to mess around with React component props)
- __INLINE_CODE_211__ (a gateway to React component lifecycle streams)
- __INLINE_CODE_212__ (to route the view events back from your wrapped React component aka view, and pass the events upstream, to parent components)
Sources
This is a Cycle.js program's input. __INLINE_CODE_213__ function shouldn't care whether something on the __INLINE_CODE_214__ is a __INLINE_CODE_215__ or some custom thing provided by one of the wrappers.
For __INLINE_CODE_216__ connected component, the __INLINE_CODE_217__ are __INLINE_CODE_218__ directly plus whatever __INLINE_CODE_219__ add to them.
Sinks
This is a Cycle.js program's output.
__INLINE_CODE_220__ invokes your __INLINE_CODE_221__ed component's __INLINE_CODE_222__ function, takes its __INLINE_CODE_223__ and subscribes the __INLINE_CODE_224__ passed from the parent component to these __INLINE_CODE_225__. Its how __INLINE_CODE_226__ is "attaching" the new component to a tree of Cycle programs.
If there's no parent component (thus, no __INLINE_CODE_227__) and your component is a "Root Cycle", then the sinks are handled to __INLINE_CODE_228__ to "run" as with a regular Cycle.js app.
React __INLINE_CODE_229__
The __INLINE_CODE_230__ed __INLINE_CODE_231__ component is essentially a view for the associated Cycle.js __INLINE_CODE_232__ function. And __INLINE_CODE_233__ encourages the view to be a "dumb" stateless function that takes __INLINE_CODE_234__ as an argument ad returns something of __INLINE_CODE_235__ type (i.e., __INLINE_CODE_236__s, __INLINE_CODE_237__, etc, whatever __INLINE_CODE_238__ component renders).
Note: The wrapped __INLINE_CODE_239__ shouldn't necessarily be a stateless functional component (SFC), you could have any __INLINE_CODE_240__ component wrapped, but its advisable to use functions for the most part as the whole point of __INLINE_CODE_241__ is to use __INLINE_CODE_242__ for anything that is not a view.
So, the __INLINE_CODE_243__ is the only communication channel from our Cycle.js part (the connected component's __INLINE_CODE_244__ function) to connected __INLINE_CODE_245__ component itself. We therefore need to pass the props from our Cycle program to the view (a __INLINE_CODE_246__ component) somehow.
This is where __INLINE_CODE_247__ comes into play and provides __INLINE_CODE_248__ source and sink for a __INLINE_CODE_249__ function. Its local to a component, so no other components will share the same __INLINE_CODE_250__ source or sink.
__INLINE_CODE_251__ sink
The __INLINE_CODE_252__ sink on __INLINE_CODE_253__ed is there to pass the props to the view:
function main(sources) {
return {
props: xs.of({ greetable: 'World' })
};
}
// Use the passed `greetable` prop
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
Remember, that the value you map to __INLINE_CODE_254__ sink should be an object of new props and that updated props will be merged with existing component __INLINE_CODE_255__ like:
const updatedProps = Object.assign({}, prevProps, newProps);
This means that you can easily overwrite any props you want and leave the others intact.
Also, note above that we're passing the __INLINE_CODE_256__ through an __INLINE_CODE_257__ stream and that might be synchronous (like in our example with a stream of static data that is sent immediately) but could also be asynchronous. In that case the value of some prop might be not yet available on the first render:
function main(sources) {
return {
props: xs.of({ greetable: 'World' }).compose(delay(3000))
};
}
// Trying to use the passed `greetable` prop
// But whooops, no `greetable` yet :(
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
So either provide a default value for that async prop's __INLINE_CODE_258__ or perform additional checks in the view, when rendering.
Note: When messing with the __INLINE_CODE_259__ in your __INLINE_CODE_260__, remember that any props that are passed from outside as an input to a __INLINE_CODE_261__ component, are also there (including callback functions, if there are any) and are also merged upon updates from outside (on __INLINE_CODE_262__).
Lets summarize: the __INLINE_CODE_263__ from the __INLINE_CODE_264__ sink are passed from the __INLINE_CODE_265__ function to the view (__INLINE_CODE_266__ component).
__INLINE_CODE_267__ source
Not only you'll need to provide the props to the view for rendering, but oftentimes you'd need your __INLINE_CODE_268__ to take part in transformations/calculations in the __INLINE_CODE_269__ function itself. For that reason, __INLINE_CODE_270__ provides the __INLINE_CODE_271__ source as well.
It allows you to have an __INLINE_CODE_272__ stream of your component's input __INLINE_CODE_273__. Note that the __INLINE_CODE_274__ is of type __INLINE_CODE_275__ and to take the actual stream of props, use its __INLINE_CODE_276__ attribute:
function main(sources) {
const name$ = sources.props.props$.map(props => props.name);
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
function MyComponent(props) {
return (
<div>
Hello, {props.name}! Your father is Mr. {props.father}.
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
...
// Somewhere else:
<MyConnectedComponent name="Luke Skywalker" father="Darth Vader" />
// renders:
//
// <div>
// Hello, LUKE SKYWALKER! Your father is Mr. Darth Vader.
// </div>
The new value on __INLINE_CODE_277__ is emitted every time __INLINE_CODE_278__ change. This could be overkill for the most cases, as you would only need just few props for your dataflow.
This is what __INLINE_CODE_279__ method for, it accepts the name of the nested prop (will return __INLINE_CODE_280__ if the stream value is not an object) and returns a stream of that prop only that is only got new value once this particular prop changes:
function main(sources) {
const name$ = sources.props.pluck('name');
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
The __INLINE_CODE_281__ed stream also performs shallow comparison and does __INLINE_CODE_282__ behind the scenes, so identical values don't cause a new value emitted on an __INLINE_CODE_283__.
Note that you don't need to always pull all props from your __INLINE_CODE_284__ function's __INLINE_CODE_285__ just for the sake of it to just forward to the __INLINE_CODE_286__ sink directly, you better only pick those you need in a dataflow with __INLINE_CODE_287__. The __INLINE_CODE_288__ passed from outside will be available on the view normally. You can overwrite them from inside your __INLINE_CODE_289__ though.
There's also __INLINE_CODE_290__ method that also accepts a nested prop name. The difference is that the result is not an __INLINE_CODE_291__ stream of prop values but the __INLINE_CODE_292__ instance itself that is scoped to that piece of __INLINE_CODE_293__:
function main(sources) {
const name$ = sources.props.pluck('name'); // Stream of strings
const nameSource = sources.props.select('someNestedObject'); // Scoped ReactPropsSource
const someNestedObjProperty$ = nameSource.pluck('someNestedObjectProperty');
...
}
This is useful when you want to __INLINE_CODE_294__ a stream of some sub-prop from deeper nested objects.
Note: Taking deep property stream this way isn't really nice currently as requires quite a bit of boilerplate - this part of API is already being thought out further and reconsidered.
Interactions
So far so good, but rendering the __INLINE_CODE_295__ isn't the only exciting thing we want to do with __INLINE_CODE_296__, right? We also want to handle events coming from the view(e.g., the DOM).
The way __INLINE_CODE_297__ handles events and nested components' callbacks is the biggest deviation from an idiomatic Cycle.js app using __INLINE_CODE_298__ driver. It refrains from a holistic "no handlers in the view" approach for a few reasons:
- We need something more in line with __INLINE_CODE_299__ as we're using React here
- We're not always handling DOM events but also need to handle some children components data/events passed back. We can't pass streams like in regular Cycle.js app, in __INLINE_CODE_300__ we do it via callbacks that are provided amongst children components' __INLINE_CODE_301__ and we need __INLINE_CODE_302__ed components to be compatible __INLINE_CODE_303__ components at all levels.
This is what "interactions" feature of __INLINE_CODE_304__ is for. It provides the unified interface for parent-child components communication and event handling that is aligned with a regular event/callback handling model on __INLINE_CODE_305__ side but also allows to access those events in form of __INLINE_CODE_306__ streams inside the __INLINE_CODE_307__ function.
Events triggering by the view
Every __INLINE_CODE_308__ component has some additional __INLINE_CODE_309__-specific props injected, so they are available on your view.
These are __INLINE_CODE_310__ which is a function that takes the __INLINE_CODE_311__ (a string) and optional __INLINE_CODE_312__ (of any type) and returns a function that will be a callback for your event or some child component prop function.
So, the signature is:
(interactionType, predefinedValue?) => (value) => void
Its supposed to be called imperatively in your view and is available as __INLINE_CODE_313__:
function MyComponent({ interact }) {
return (
<div>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
For the sake of being cleaner (and for better TypeScript support), __INLINE_CODE_314__ also leverages the ES2015 __INLINE_CODE_315__ feature to provide the object with dynamic properties, each representing an __INLINE_CODE_316__ with __INLINE_CODE_317__ dynamically defined after the function property name. Its available on __INLINE_CODE_318__ so we can rewrite the above snippet as:
function MyComponent({ interactions }) {
return (
<div>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
In the example above, the __INLINE_CODE_319__ function receives the click __INLINE_CODE_320__ as an argument and that is what you'll get in your __INLINE_CODE_321__ (next section).
While cleaner, it doesn't allow for the __INLINE_CODE_322__ to be set, so if you need some particular value to be emitted by the associated stream in your __INLINE_CODE_323__, you can either use __INLINE_CODE_324__ or create an inline function:
// Assuming you have `id` available, e.g.
// when looping/mapping over the collection of items
<button
type="button"
className="delete-btn"
onClick={interact('delete', id)}
>
Click me!
</button>
// OR
<button
type="button"
className="delete-btn"
onClick={() => interactions.delete(id)}
>
Click me!
</button>
View events handling with __INLINE_CODE_325__ source
There's internal __INLINE_CODE_326__ in __INLINE_CODE_327__ and it provides the __INLINE_CODE_328__ source for our __INLINE_CODE_329__ function. The __INLINE_CODE_330__ source is of type __INLINE_CODE_331__, which has the __INLINE_CODE_332__ method, which we can use to obtain interaction streams:
function main(sources) {
const buttonClick$ = sources.interactions.select('buttonClick');
// Do something with a buttonClick$, e.g.
// map it to the click coordinates props
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interact, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
The streams are __INLINE_CODE_333__ed by the same __INLINE_CODE_334__ string you provided to the __INLINE_CODE_335__ function on your view.
Similar to how __INLINE_CODE_336__ object with dynamic function-props is available on your view, __INLINE_CODE_337__ is a ES2015 __INLINE_CODE_338__ and it maintains the dynamic streams right on itself (just don't use __INLINE_CODE_339__ as its reserved method name here here):
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interactions, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
Note: If you try to get the non-existing stream, it will still work, this thing is dynamic, so the stream will be created nevertheless but will just never emit values. Be careful and look out for typos! Using TypeScript is highly recommended to avoid typos here.
Upstream interactions with __INLINE_CODE_340__ sink (calling callbacks on __INLINE_CODE_341__).
Now that we can handle the streams of view events and values from the children components, we would also need to somehow call the callbacks that are passed among our __INLINE_CODE_342__.
First, nothing prevents from following the common React way and call those directly from the view:
function MyComponent({ onButtonClick }) {
return (
<button type="button" onClick={onButtonClick}>
Click me!
</button>
);
}
// Somewhere else:
doSomething = (event) => { console.log('clicked!'); };
...
<MyComponent onButtonClick={doSomething} />
This is fine if you don't need that value's stream in your dataflow. This approach bypasses your Cycle.js program's __INLINE_CODE_343__ completely and doesn't make it impure by otherwise possibly passing an effectful function as __INLINE_CODE_344__. There's even no need to __INLINE_CODE_345__ your component in the above example.
Keep in mind that if __INLINE_CODE_346__ is optional there, you will also need to check whether that is defined before providing that as a callback (use __INLINE_CODE_347__ or TypeScript to mark it as either required or optional).
Oftentimes you will need to call some callback that lives on the __INLINE_CODE_348__ though, and this is what __INLINE_CODE_349__ sink provided by __INLINE_CODE_350__ does:
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
const coords$ = buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
);
// We both update the `props` (to render it in this component)
// and pass upstream
return {
props: coords$,
interactions: xs.of({
onCoordsChange: coords$
})
};
}
function MyComponent({ interactions }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
handleCoordsChange = (coords) => {
console.log('New coordinates received: ', coords);
};
...
<ConnectedComponent onCoordsChange={handleCoordsChange} />
// The new coords will be rendered by the component
// and the console will show the "New coordinates received"
// with new coords too.
Once the appropriate stream emits a value, that callback is called. It will also check if the callback was passed in the first place to skip the call attempt if no callback with a given name is passed among the __INLINE_CODE_351__.
Important: Note that __INLINE_CODE_352__ sink expects a stream of object whose property names correspond to callback names on __INLINE_CODE_353__ and property values are themselves streams too - whatever values those streams emit, will be passed as an argument to the callback.
So far so good, but we know that our __INLINE_CODE_354__ is a wrapper and not a driver, so what if one passed the effectful function as a callback, e.g., __INLINE_CODE_355__ passed as __INLINE_CODE_356__ above does a __INLINE_CODE_357__ internally - that is a side effect, right?
Well, this is why the function is not invoked directly, or inside the wrapper (since the wrapper is the part of the pure land of our __INLINE_CODE_358__s), its using the __INLINE_CODE_359__ driver internally. More on this below in a section about Side-effect control, just keep in mind that __INLINE_CODE_360__ and __INLINE_CODE_361__ should be pure and __INLINE_CODE_362__ follows that requirement.
React component lifecycle streams
You're probably not going to need __INLINE_CODE_363__ component lifecycle hooks often inside your __INLINE_CODE_364__, but still - they are provided via special __INLINE_CODE_365__. It provides the __INLINE_CODE_366__ source that is available on each __INLINE_CODE_367__ed __INLINE_CODE_368__ sources.
The lifecycle events are provided as __INLINE_CODE_369__ streams and their names mostly mirror the React lifecycle methods, but shortened to remove some redundant __INLINE_CODE_370__ prefixes:
React lifecycle method
__INLINE_CODE_371__ source property
Stream value
__INLINE_CODE_372__
__INLINE_CODE_373__
__INLINE_CODE_374__
__INLINE_CODE_375__
__INLINE_CODE_376__
__INLINE_CODE_377__
__INLINE_CODE_378__
__INLINE_CODE_379__
__INLINE_CODE_380__
__INLINE_CODE_381__
__INLINE_CODE_382__
__INLINE_CODE_383__
__INLINE_CODE_384__
__INLINE_CODE_385__
__INLINE_CODE_386__
__INLINE_CODE_387__
__INLINE_CODE_388__
__INLINE_CODE_389__
__INLINE_CODE_390__
__INLINE_CODE_391__
Error data
The "error data" on the __INLINE_CODE_392__ is an object of this shape:
{
error: Error;
errorInfo: ErrorInfo;
}
These are basically React __INLINE_CODE_393__ arguments squashed into a single object.
Also, note that __INLINE_CODE_394__ hook is not present there, since that is not really an event and affects how your component is rendered. __INLINE_CODE_395__ would be providing its own custom internal __INLINE_CODE_396__ instead for optimisation purposes and you could implement it on your wrapped component anyway.
Note: As well, no lifecycle events are passing anything related to React component __INLINE_CODE_397__, only taking __INLINE_CODE_398__ into account instead. This is because the __INLINE_CODE_399__ is meant to be managed differently when Cycle.js is in use. You can still use that for wrapped component but its out of play on the component generated by __INLINE_CODE_400__.
A quick example:
function main(sources) {
const willUnmount$ = { sources.lifecycle };
// Do something on unmounting, e.g. report that to a console
// via some hypothetical `logger` driver
return {
logger: willUnmount$.mapTo(`I'll be unmounted soon!`)
};
}
Side-effect control and __INLINE_CODE_401__ driver
Side-effect control is where Cycle.js really shines (not only implementation, it applies to the "cycle pattern" behind Cycle.js in the first place).
In order to keep the __INLINE_CODE_402__ part of __INLINE_CODE_403__ed component pure, we need to make sure that any potentially effectful function is not run inside our __INLINE_CODE_404__ directly.
This is what __INLINE_CODE_405__ driver is responsible for. It allows to only describe what function you want to call and with what arguments, then delegate that to a __INLINE_CODE_406__ which is supposed to run any impure functions outside of the app itself.
The way it works is very similar to that of __INLINE_CODE_407__ driver from __INLINE_CODE_408__ package where you don't actually run the request inside your __INLINE_CODE_409__ but just describe it and pass to __INLINE_CODE_410__ driver to handle. __INLINE_CODE_411__ driver is just a bit more generic and operates on functions.
__INLINE_CODE_412__ uses __INLINE_CODE_413__ driver internally (to run effects for upstream __INLINE_CODE_414__), but it is also available for you on any connected __INLINE_CODE_415__ as __INLINE_CODE_416__ sink automatically (i.e., without explicit configuration on a root cycle):
function log() {
console.log(`I'm effectful!`);
}
function saveToLocalStorage(message) {
localStorage.setItem('message', message);
}
function main(sources) {
const someCallbackFn$ = sources.props.pluck('someCallbackFunction');
return {
fnCallEffect: xs.merge(
xs.of({ fn: log }),
xs.of({ fn: saveToLocalStorage, args: ['some message to save'] }),
// Call the `props` callback function directly, this is how
// `interactions` sink works behind the scenes:
someCallbackFn$.map(fn => ({ fn }))
)
};
}
Isolation
Isolation is Cycle.js way of making the component's __INLINE_CODE_417__ and __INLINE_CODE_418__ isolated from influence of other components.
Make sure to carefully read more about Cycle.js isolation here. It might take a bit to get used to an idea.
To reiterate: the concept of isolation is important for Cycle.js apps. Its a little bit different for __INLINE_CODE_419__ because most things are scoped to a particular __INLINE_CODE_420__ed component already, like components have "local" __INLINE_CODE_421__, __INLINE_CODE_422__ and __INLINE_CODE_423__ sources and sinks and __INLINE_CODE_424__ components are also self-contained already.
There are though __INLINE_CODE_425__/__INLINE_CODE_426__ that will need the isolation feature, like __INLINE_CODE_427__ driver or __INLINE_CODE_428__ wrapper (a state management solution for Cycle.js apps). So, isolation makes sense for shared __INLINE_CODE_429__/__INLINE_CODE_430__, those you pass to __INLINE_CODE_431__ function (by defining the __INLINE_CODE_432__/__INLINE_CODE_433__ options on root __INLINE_CODE_434__) and that will be available across the entire tree of Cycle programs.
Note: The isolation works by scoping the source streams to particular component and a kind of unscoping of the sink streams before connecting to parent's sinks.
Also note, that each driver/wrapper decides on its own how its sources/sinks should be isolated.
In Cycle.js, you use the __INLINE_CODE_435__ function from __INLINE_CODE_436__ package directly, as you're just invoking the children __INLINE_CODE_437__ functions from your current component's __INLINE_CODE_438__ function:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// Do something with childSinks, combine somehow, etc
return {
...
};
}
__INLINE_CODE_439__ function has this simple signature:
isolate :: (MainFn, scope) => MainFn
It takes the __INLINE_CODE_440__ function and an optional __INLINE_CODE_441__ and returns an isolated __INLINE_CODE_442__ function.
__INLINE_CODE_443__ does also use the __INLINE_CODE_444__ package internally, but you can't call __INLINE_CODE_445__ directly on some __INLINE_CODE_446__ function. You'd never call one component's __INLINE_CODE_447__ from the other component's __INLINE_CODE_448__ directly with __INLINE_CODE_449__.
Naive explicit isolation (conceptually incorrect)
Just to illustrate the issue, there's an attempt to isolate the component's __INLINE_CODE_450__ sources, so that HTTP requests initiated from that components are scoped to it (and are unavailable to siblings).
Since we know that __INLINE_CODE_451__ returns a __INLINE_CODE_452__ function and __INLINE_CODE_453__ takes the __INLINE_CODE_454__ function as an argument, lets try to feed it an isolated version of it:
function main(sources) {
...
}
function MyComponent() {
...
}
const isolatedMain = isolate(main, { HTTP: 'myComponent' });
const ConnectedComponent = cycleConnect(isolatedMain)(MyComponent);
While this will won't raise any errors (because this is valid code after all), this approach brings a subtle issue.
This will isolate things for this particular components but not their children components, so in this case, children (connected childred __INLINE_CODE_455__ components) will have no __INLINE_CODE_456__ isolation scope for __INLINE_CODE_457__ channel.
This might seems like a no-issue, because hey, you can define different scopes for the children components right? Well, while this is technically possible, its conceptually incorrect and it will be completely broken in some cases, e.g., with __INLINE_CODE_458__ where the deeper you go - the "more filtered" source streams you get.
Important! The above note about it being "conceptually incorrect" refers to the way __INLINE_CODE_459__ maintains the tree of Cycle.js programs internally. It tries to make the entire app structure look the same way as you'd manually write a Cycle.js app. So, when you __INLINE_CODE_460__ at some level, then if inside your __INLINE_CODE_461__ you also call some __INLINE_CODE_462__ happens, then __INLINE_CODE_463__ gets isolated sources no matter what.
__INLINE_CODE_464__ option for __INLINE_CODE_465__
The first way to correctly isolate __INLINE_CODE_466__ed component is to use the __INLINE_CODE_467__ option for __INLINE_CODE_468__ itself, so the above can be rewritten as:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main, {
isolate: { HTTP: 'myComponent' }
})(MyComponent);
This will work perfectly, with all children __INLINE_CODE_469__ed components having __INLINE_CODE_470__ namespace applied to __INLINE_CODE_471__ sources/sinks in their __INLINE_CODE_472__ functions.
The __INLINE_CODE_473__ option accepts a __INLINE_CODE_474__ or the __INLINE_CODE_475__ and in that case its passed directly to __INLINE_CODE_476__ package's __INLINE_CODE_477__ as a __INLINE_CODE_478__ argument. So, check out the __INLINE_CODE_479__ function API reference.
__INLINE_CODE_480__ also makes it accept a function that takes component __INLINE_CODE_481__ as an argument and should return the scope of __INLINE_CODE_482__ or __INLINE_CODE_483__ type that is then also passed to __INLINE_CODE_484__ package's __INLINE_CODE_485__. This allows for some dynamic isolation scopes to be set based on the React component's initial props.
Important: Despite the __INLINE_CODE_486__ is provided as an option to __INLINE_CODE_487__ when defining the connected component, the actual isolation will only happen when component instance is created. This is why its possible to dynamically resolve the __INLINE_CODE_488__ based on the component __INLINE_CODE_489__ in __INLINE_CODE_490__.
__INLINE_CODE_491__ prop
Despite the __INLINE_CODE_492__ option will work nicely for some cases, there's a pattern in how it is utilized in pure Cycle.js programs. Lets take a look at pure Cycle.js example again:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// ...
return {
...
};
}
The important part is that in Cycle.js app its not a child who decides how to isolate itself but __INLINE_CODE_493__ is provided from the parent instead.
Most of the time, we want to maintain the same approach with __INLINE_CODE_494__. The advantage is that component in this case is totally agnostic from the provided scope, it doesn't even care whether its isolated or not at all - all it knows is that it expects certain __INLINE_CODE_495__ and returns certain __INLINE_CODE_496__. This makes the component reusable.
This is especially important with __INLINE_CODE_497__, where you want to control where your child components "attach" to the state.
In other words, the parent decides how to compose children components, not the other way around, because otherwise the child would need to have an intimate knowledge of something that really belongs to parent and thus not being reusable and breaking encapsulation.
For this reason, every __INLINE_CODE_498__ed React component accepts an __INLINE_CODE_499__ prop, so you can use that just like any other prop:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
// Isolate everything
<ConnectedComponent isolate="myComponent" />
// Isolate HTTP only
<ConnectedComponent isolate={{ HTTP: 'myComponent' }} />
<ConnectedComponent isolate={{ HTTP: 'myComponent_2' }} />
In contrast with __INLINE_CODE_500__ option, the __INLINE_CODE_501__ prop can only accept __INLINE_CODE_502__ or __INLINE_CODE_503__ that is directly passed to __INLINE_CODE_504__ function from __INLINE_CODE_505__, thus maintaining full parity with pure Cycle.js approach. Indeed, having a function that accepts initial __INLINE_CODE_506__ wouldn't make much sense because you're anyway providing props that could be different for different component instances when rendering that component.
__INLINE_CODE_507__ helper component
Since isolation in __INLINE_CODE_508__ applies to both "current" __INLINE_CODE_509__ed component and all its __INLINE_CODE_510__ed children, we might sometimes need to define some connected component to only be a common parent for some children, which must provide isolation but wouldn't contain any logic.
This is what a helper __INLINE_CODE_511__ component for. It accepts the __INLINE_CODE_512__ prop (that is directly passed as a __INLINE_CODE_513__ argument to __INLINE_CODE_514__ function of __INLINE_CODE_515__):
<Isolator scope={{ HTTP: 'someNamespace' }}>
<ConnectedComponent someProps="1" />
<ConnectedComponent someProps="2" />
<ConnectedComponent someProps="3" />
</Isolator>
This assists the isolation composition at view level.
Extra: State management with __INLINE_CODE_516__
__INLINE_CODE_517__ is the idiomatic Cycle.js fractal state management solution. It is tailored for Cycle.js apps but since __INLINE_CODE_518__ is using the real Cycle.js, it stays fully compatible here as well.
Using __INLINE_CODE_519__ with __INLINE_CODE_520__ is optional, since you might be using different lib (e.g., __INLINE_CODE_521__) for that.
To start, just read the onionify docs and install the package:
$ npm install --save cycle-onionify
and pass that with __INLINE_CODE_522__ array to a __INLINE_CODE_523__ connected component (e.g., Root Cycle):
// RootCycle.js
import { run } from '@cycle/run';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
wrappers
})();
export default RootCycle;
Now every __INLINE_CODE_524__ed component that is a child of the above-mentioned __INLINE_CODE_525__ in React component tree got the __INLINE_CODE_526__ source and sink.
Important: Notice that you're all set - its purely a Cycle.js land. __INLINE_CODE_527__ doesn't provide any additional functionality for __INLINE_CODE_528__. Instead, it provides few helpers for isolation and working with collections on the React side.
All the exportables related to __INLINE_CODE_529__ reside in the extra package: __INLINE_CODE_530__. It is installed along with the main lib but is not __INLINE_CODE_531__d/__INLINE_CODE_532__ed by default, so won't be bundled (by Webpack, Rollup, etc) unless you're using it.
This extra sub-package contains few things:
- __INLINE_CODE_533__ helper component
- __INLINE_CODE_534__ helper component
- __INLINE_CODE_535__ helper factory function
Read ahead for details on each.
In __INLINE_CODE_536__, the state/reducers layer composition is done via isolation, so the first thing here is __INLINE_CODE_537__:
__INLINE_CODE_538__ helper component
This is pretty much like __INLINE_CODE_539__ but is here to specifically isolate __INLINE_CODE_540__ sources/sinks only.
So, the:
import { StateIsolator } from 'react-cycle-connect/lib/extra/onionify';
// ...
<StateIsolator lens="someStateProperty">
<NestedComponent />
</StateIsolator>
is somewhat a convenient shortcut to:
import { Isolator } from 'react-cycle-connect';
// ...
<Isolator scope={{ onion: 'someStateProperty', '*': null }}>
<NestedComponent />
</Isolator>
Notice how __INLINE_CODE_541__'s prop is explicitly named __INLINE_CODE_542__. It just implies that oftentimes you'll be using "lens" to isolate the state. Consider a string just a shortcut to a more elaborate __INLINE_CODE_543__ object.
In the example above, the __INLINE_CODE_544__'s __INLINE_CODE_545__ function __INLINE_CODE_546__ will represent only that "someStateProperty" piece of state. Pretty much how you'd __INLINE_CODE_547__ the __INLINE_CODE_548__ channel in an idiomatic Cycle.js app.
There's also a __INLINE_CODE_549__ property, so that you can change the name of the source/sink inside your __INLINE_CODE_550__ to something else. Thats to be on par with __INLINE_CODE_551__ features, where you can also change that name.
Handling collection state manually
Managing collections of components in pure Cycle.js with __INLINE_CODE_552__ is a kind of a recognized challenge. This is why __INLINE_CODE_553__ ended up providing __INLINE_CODE_554__ helper function - see their docs for more detail.
While __INLINE_CODE_555__ also has a __INLINE_CODE_556__ helper component that is very similar to __INLINE_CODE_557__ __INLINE_CODE_558__ function, lets first try to pretend we don't have it and imagine how'd we handle the state without it.
Note: Nothing prevents you from skipping right to a description of how __INLINE_CODE_559__ works, but it might be useful to understand the rationale behind it, so reading this section is recommended.
Handling collections is different with React than in Cycle.js, e.g., you can always just render React components in a loop or __INLINE_CODE_560__ an array of data to an array of components and React will render that.
When you want to render some __INLINE_CODE_561__ data from your state, its easy without any helpers:
/*
Assume you have this value on your `List` component `stateFork
- updated to more recent version of cycle, including @cycle/state
- updated to React 16.9 api, including new lifecycles and context api
- merged onionify separate lib so import is __INLINE_CODE_0__
- added tests
- published at @iilab/react-cycle-connect
react-cycle-connect
An experimental library that allows using Cycle.js as a reactive dataflow framework for React.
Written in TypeScript.
This little thing allows to define your app's view layer using regular React components, but implement the actual logic behind it using pure functional reactive approach, taking advantage of Cycle.js' Model-View-Intent pattern and its exceptional side effect control.
This isn't a custom implementation of Cycle pattern for __INLINE_CODE_1__ but a link to real Cycle.js instead, so any Cycle.js drivers and wrappers can be applied (e.g., __INLINE_CODE_2__ for state management). Also, for this reason, some knowledge about how __INLINE_CODE_3__ works is assumed.
Note: This is an experimental lib and currently works with __INLINE_CODE_4__ and __INLINE_CODE_5__ only. If all goes good, the support for __INLINE_CODE_6__/__INLINE_CODE_7__ (instead of __INLINE_CODE_8__) and for __INLINE_CODE_9__/__INLINE_CODE_10__ (instead of __INLINE_CODE_11__) is anticipated. Nothing prevents from having __INLINE_CODE_12__ covered too.
UPDATE (MAR 2018): Some internals overhaul is pending and it's currently waiting for Cycle.js to settle on hybrid push-pull reactivity and have internals rewritten to use Callbags instead of __INLINE_CODE_13__. So, __INLINE_CODE_14__ is going to be using Callbags by default too.
Any feedback is highly appreciated!
Table of Contents
- react-cycle-connect
- Table of Contents
- Shup up and show me the code
- Installation
- Motivation
- How it works (architecture)
- Guide
- API reference
- Alternatives / Competitors
- FAQ
- Roadmap
Shup up and show me the code
Cycle.js's counter example ported over:
import React from 'react';
import ReactDOM from 'react-dom';
import xs from 'xstream';
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
// Your Cycle.js `main` function
function main(sources) {
const action$ = xs.merge(
sources.interactions.decrement.map(ev => -1),
sources.interactions.increment.map(ev => +1)
);
const count$ = action$.fold((acc, x) => acc + x, 0);
return {
props: count$.map(count => ({ count }))
};
}
// Your React component
function App({ interactions, count }) {
return (
<div>
<button onClick={interactions.decrement}>Decrement</button>
<button onClick={interactions.increment}>Increment</button>
<p>Counter: {count}</p>
</div>
);
}
// Some connection magic
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
// Render normally as it's a regular React app
ReactDOM.render(
<ConnectedApp />,
document.getElementById('root')
);
Also - there's official TodoMVC example with __INLINE_CODE_33__ for state management.
Installation
Using npm:
$ npm install --save react-cycle-connect
Using yarn:
$ yarn add react-cycle-connect
Motivation
The motivation to create this lib was based off the need to retain the React-style composability of the view layer using regular React components, reuse existing React components, make it (the lib) easily/incrementally adoptable in an ongoing React project, but also take advantage of the Cycle.js' MVI pattern and side effect control.
At the same time, while the most valuable thing from __INLINE_CODE_34__ is the pattern itself, there are numerous available libs/plugins for Cycle.js to take advantage of, letting us take the best of both worlds.
This way, you can still use anything from React stack (e.g., __INLINE_CODE_35__ to compose your routed app screens), but describe the interactions as a dataflow, with pure functions and reactive streams. Most of React components are going to be "dumb" (and also stateless functional aka SFC) since all the backing logic and cross-component communication would be handled by a tree of __INLINE_CODE_36__ programs.
Why not just use Cycle.js alone?
That is what I'd recommend considering in the first place. Its fast, its cool, the __INLINE_CODE_37__ (virtual DOM rendering engine behind Cycle's DOM driver) is goddamn fast and reliable. If you don't intend to use __INLINE_CODE_38__ components in any way, don't even get bothered. The __INLINE_CODE_39__ is more about "introducing Cycle.js to __INLINE_CODE_40__", not the other way around.
Is it a "Cycle.js in React" or "React in Cycle.js"?
More of a "Cycle.js in React". The app is bootstrapped by rendering a React component to the DOM, view is composed the React way. The root Cycle.js program is being __INLINE_CODE_41__ by rendering a root __INLINE_CODE_42__ed component. Though, it doesn't prescribe how you write your Cycle.js programs, and aside from the DOM generation part, its just Cycle.js all the way.
How it works (architecture)
Every __INLINE_CODE_43__ed component got a real Cycle.js program "attached" to it. The boundary between the two is __INLINE_CODE_44__. From your Cycle.js program standpoint, __INLINE_CODE_45__ is a view. Cycle.js program can update __INLINE_CODE_46__ that React component will then render.
A bunch of nested React components in your app represent a "tree of React components". __INLINE_CODE_47__ maintains a "tree of Cycle.js programs" that map to __INLINE_CODE_48__ed components and takes advantage of React's context feature to provide links between the nodes. Each connected component provides scoped links to the children nodes .
New "Cycle nodes" are attached lazily, upon associated React component initialization (this is where React controls how Cycle programs are run). The above-mentioned "link" is just an object on React __INLINE_CODE_49__ that brings couple properties:
- __INLINE_CODE_50__ for the child connected component's __INLINE_CODE_51__ function; and
- __INLINE_CODE_52__ to subscribe to that component's __INLINE_CODE_53__ function __INLINE_CODE_54__.
This is where something unusual for the Cycle.js happens - we subscribe to and unsubscribe from connected component's __INLINE_CODE_55__ function __INLINE_CODE_56__ based on React component's lifetime hooks. In Cycle.js, you normally just run the app at startup and let it run as an ongoing process. But this is where __INLINE_CODE_57__ performs the sync between the two worlds.
Overall, aside from using React __INLINE_CODE_58__ (which is just an implementation detail) and some intense wrapping on the __INLINE_CODE_59__ side to make laziness possible, __INLINE_CODE_60__ tries to represent the entire tree of Cycle.js programs as close as possible to what you'd end up with by writing idiomatic Cycle.js with explicit calling of nested component's __INLINE_CODE_61__ functions from the parent component __INLINE_CODE_62__ function. The result of children components is always __INLINE_CODE_63__d.
Aside from passing the __INLINE_CODE_64__ to children components, __INLINE_CODE_65__ also explicitly applies some internal wrappers to integrate with React component: __INLINE_CODE_66__, __INLINE_CODE_67__ and __INLINE_CODE_68__, that are scoped to a connected component's Cycle.js program only. It also explicitly runs the internal __INLINE_CODE_69__ driver for the __INLINE_CODE_70__ nodes by default - this is necessary for the __INLINE_CODE_71__ sink to work (because its supposed to run callbacks passed via __INLINE_CODE_72__ and that might be an effectful function).
Note that the entire internal "tree" of Cycle.js programs is maintained pure, including internal component-scoped wrappers, just as you would do with regular Cycle.js program.
Differences from idiomatic Cycle.js usage
Note: there's a difference in how'd you write the __INLINE_CODE_73__/__INLINE_CODE_74__ app vs pure __INLINE_CODE_75__ app. There's no misuse of Cycle.js per se, we just don't use it for rendering with __INLINE_CODE_76__ - and that applies certain implications to the approach.
__INLINE_CODE_77__ and __INLINE_CODE_78__, while both allow to create user interfaces, imply different approaches to the app architecture. The most important part here is that they are conceptually different when it comes to generating the VDOM.
In order to combine the two approaches nicely, there are certain deviations from how'd you write the __INLINE_CODE_79__-only app to allow for more optimal React DX.
The main difference from a pure __INLINE_CODE_80__ way is that its not a Cycle program that technically generates VDOM - instead, there's React component to represent a piece of UI. __INLINE_CODE_81__ approach to view/__INLINE_CODE_82__ event handling is rather purist - view only defines the markup and nothing else (i.e., user interaction/event handlers are defined inside Cycle program itself and not as some view component __INLINE_CODE_83__/etc attribute). That is not a crazy random opinion but instead a very well thought and weighted out decision by Cycle.js team. __INLINE_CODE_84__ takes a different approach, which isn't that strict and is more in line with React, with callbacks amongst __INLINE_CODE_85__ and all that stuff. It enables the events/component callbacks handling via interactions.
The view composition is obviously completely different. In __INLINE_CODE_86__ you run each component's __INLINE_CODE_87__ function that (possibly) generates a VDOM node and then explicitly combine the pieces of markup into a single VDOM stream that is then given to a DOM driver. With __INLINE_CODE_88__ you compose the view layer with React components normally. You can just connect those to Cycle.js program to "outsource" the actual logic to it.
Guide
This assumes the preliminary knowledge of both __INLINE_CODE_89__ and __INLINE_CODE_90__ (and __INLINE_CODE_91__). It doesn't provide much of the insight on how these work, but is instead focused on how __INLINE_CODE_92__ combines the two.
Basics
The result of __INLINE_CODE_93__ call is a HOC that optionally takes a React component (similar to that __INLINE_CODE_94__ function of __INLINE_CODE_95__) and returns a component that is augmented with backing Cycle.js program. This Cycle.js program is internally wrapped into a "Cycle Node" that, when initialized, is connected to a tree of Cycle.js programs. "Cycle Nodes" of every __INLINE_CODE_96__ed components are linked together via React __INLINE_CODE_97__.
Note, though, that there are no direct references from one component's Cycle program to another component's Cycle program, i.e., you're not invoking one __INLINE_CODE_98__ function from the other (this is perfectly possible though, but since we're not using __INLINE_CODE_99__ here, it would only handle non-DOM stuff). Instead, __INLINE_CODE_100__ handles the linking behind the scenes, so the "sibling" Cycle programs are fed with the same __INLINE_CODE_101__ and subscribe their __INLINE_CODE_102__ to the same __INLINE_CODE_103__.
There's essential concept of isolation in Cycle.js, so every __INLINE_CODE_104__ed component accepts an __INLINE_CODE_105__ prop, that is directly passed to __INLINE_CODE_106__ function from __INLINE_CODE_107__ package. Read more about Cycle.js isolation here.
Root Cycle
Every __INLINE_CODE_108__ed component provides a __INLINE_CODE_109__ object to its children through React __INLINE_CODE_110__ and also expects that __INLINE_CODE_111__ from some parent component (not necessarily immediate parent).
That link object has the following shape:
{
// Sources to provide to our Cycle.js program's `main` function as an argument
sources: { ... },
// sinkProxies to subscribe to our Cycle program's `main` function sinks
sinkProxies: { ... }
}
You shouldn't be working with that directly but its important that something needs to provide the top-level __INLINE_CODE_112__ and __INLINE_CODE_113__ in the first place. This is what __INLINE_CODE_114__ option of cycle connect for.
Also, the __INLINE_CODE_115__ed components' programs (__INLINE_CODE_116__ functions) are lazily attached to already running upper-level Cycle.js program, so the __INLINE_CODE_117__ program has to be __INLINE_CODE_118__.
So, root __INLINE_CODE_119__, aside from __INLINE_CODE_120__ option, also needs __INLINE_CODE_121__ defined, so that it knows how to run your top-level Cycle.js program. This would normally be just a __INLINE_CODE_122__ function from __INLINE_CODE_123__ package (or __INLINE_CODE_124__, or __INLINE_CODE_125__, depending on the reactive streams library in use).
You could just __INLINE_CODE_126__ your top-level __INLINE_CODE_127__ component like we did in the first example:
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
...
function main(sources) {
...
}
function App() {
return (
// Some JSX
...
);
}
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
Though this will work, its advisable to create a specialized __INLINE_CODE_128__ component just for this purpose:
// RootCycle.js
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
function main(sources) {
...
}
export const RootCycle = cycleConnect(main, {
root: true,
runFn: run
})(); // No component to wrap? Its okay, read ahead
export default RootCycle;
The top-level Cycle sources are actually __INLINE_CODE_129__ and __INLINE_CODE_130__, so root Cycle is also in charge of providing these __INLINE_CODE_131__ and __INLINE_CODE_132__ to any Cycle.js program down the tree, as __INLINE_CODE_133__.
__INLINE_CODE_134__ option is the object with keys defining properties of the __INLINE_CODE_135__ object, identical to how you'd do that in a regular Cycle.js program.
__INLINE_CODE_136__ option accepts an array of wrappers (order might matter there, so its a user-land option).
Also, the __INLINE_CODE_137__ function is optional (because you might need a __INLINE_CODE_138__ to be a __INLINE_CODE_139__ or for __INLINE_CODE_140__ only), so you can skip it, unless you have some logic to implement there.
Note that the __INLINE_CODE_141__ can be passed to any __INLINE_CODE_142__, even non-root, but __INLINE_CODE_143__ option is only valid for __INLINE_CODE_144__ Cycle and is ignored otherwise.
// RootCycle.js
import { run } from '@cycle/run';
import { makeHTTPDriver } from '@cycle/http';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const drivers = { HTTP: makeHTTPDriver() };
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
export default RootCycle;
__INLINE_CODE_145__ component is basically in charge of bootstrapping the "Cycle" part of your app here.
Then just wrap your __INLINE_CODE_146__ with it and e.g., render with __INLINE_CODE_147__:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import RootCycle from './RootCycle';
ReactDOM.render(
(
<RootCycle>
<App />
</RootCycle>
),
document.getElementById('root')
);
__INLINE_CODE_148__ option
You might have noticed in axamples above that we're not providing any component to a __INLINE_CODE_149__ HOC, leaving empty braces instead:
const ConnectedComponent = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
Well, that is not a mistake - the wrapped component is optional. When a "component to wrap" is provided to __INLINE_CODE_150__, then if we render the connected component, the __INLINE_CODE_151__ will also render __INLINE_CODE_152__ internally.
Thats all good and works (and what you'll be using most of the time anyway), but sometimes you just want to keep your React Element tree a bit cleaner when the wrapped component would be a kind of noop anyway (just forwarding __INLINE_CODE_153__ and nothing else), or component is some technical/lower-level like __INLINE_CODE_154__, or have some special rendering behavior.
So, we want to avoid seeing this in React Developer Tools:
<cycleConnect(SomeComponent)>
<SomeComponent>
...
</SomeComponent>
</cycleConnect(SomeComponent)>
You can use __INLINE_CODE_155__ option for that. It accepts the function with this signature:
(props) => ReactNode | null
If no component is provided for wrapping, nor __INLINE_CODE_156__ option is defined, the __INLINE_CODE_157__ option defaults to __INLINE_CODE_158__ function, so its either forwarding __INLINE_CODE_159__ if any or renders nothing.
If both __INLINE_CODE_160__ option is provided and component-to-wrap is passed, then __INLINE_CODE_161__ takes the precedence.
Note: Why not use the __INLINE_CODE_162__ option in place of wrapped component all the time? Well, its technically possible, espeically if your React component is a stateless functional component anyway.
But regular HOC wrapping is just common technique in React world and is nicely composable when you have other HOCs than __INLINE_CODE_163__.
And is cleaner too - you might be prototyping your app as a set of __INLINE_CODE_164__ components first, without even involving __INLINE_CODE_165__, then "connect" them all at once, or introduce __INLINE_CODE_166__ incrementally. Whatever :)
__INLINE_CODE_167__ option
Your component's __INLINE_CODE_168__ is by default automatically assigned to the following (depending on whether the wrapped component is provided, or __INLINE_CODE_169__ option, or neither:
- __INLINE_CODE_170__ when WrappedComponent is passed
- __INLINE_CODE_171__ when __INLINE_CODE_172__ prop is defined (and is truthy)
- __INLINE_CODE_173__ otherwise
You can override that like with any regular React component:
const ConnectedComponent = cycleConnect({
...
)(Component);
ConnectedComponent.displayName = 'ConnectedComponent';
But there's also a __INLINE_CODE_174__ option to set the generated (connected) component __INLINE_CODE_175__, which looks a bit cleaner:
const ConnectedComponent = cycleConnect({
...
displayName: 'ConnectedComponent'
)(Component);
Inside the Cycle
__INLINE_CODE_176__ really assumes that you know how to use Cycle.js. Its really a very simple yet very elegant pattern, so make sure to familiarize yourself with it.
Quick recap on what Cycle.js __INLINE_CODE_177__ function is. Its basically a pure function with the signature:
main :: (Sources) => Sinks
It accepts __INLINE_CODE_178__ as an argument and returns __INLINE_CODE_179__ and describes a dataflow using composition of reactive streams and their transformations with pure functions.
Please note that the __INLINE_CODE_180__ function is only run once per __INLINE_CODE_181__ component lifecycle. It doesn't run again on rerendering or something. This is because the actual call to __INLINE_CODE_182__ is to just build up a dataflow, not to get some rendering data.
The data flows through reactive streams, from __INLINE_CODE_183__ to __INLINE_CODE_184__ of the __INLINE_CODE_185__, until its __INLINE_CODE_186__ are unsubscribed from parent Cycle program's __INLINE_CODE_187__ (this happens when React component is unmounted).
Drivers
The __INLINE_CODE_188__ is supposed to run side effects for a Cycle.js program. Its outside the program itself, supposed to be impure and connects your Cycle app with the "outer world". This is an essential concept and is described in a great detail in Cycle.js docs on drivers.
There are no differences from idiomatic Cycle.js in how __INLINE_CODE_189__ handles __INLINE_CODE_190__ - it just hands it over to __INLINE_CODE_191__ directly. There is only one __INLINE_CODE_192__ driver (an __INLINE_CODE_193__ driver) that is always added internally by the __INLINE_CODE_194__ed component - it allows to invoke functions outside the __INLINE_CODE_195__ that is to be maintained pure.
Wrappers
The __INLINE_CODE_196__ is a rather simple thing - its just a function that takes __INLINE_CODE_197__ function that wraps it with the other __INLINE_CODE_198__ function that might in turn add some logic in between:
function someWrapper(originalMain) {
return wrappedMain(sources) {
// Can do something here, like transforming `sources` somehow
// ...
// Invoke the original `main` internally to obtain its sinks
const sinks = originalMain(sources);
// Can do something to `originalMain`s sinks here
// ...
return {
...sinks,
someOtherSink: xs.of({ ... }) // Can add a new sink
};
}
}
It is important that the wrapping __INLINE_CODE_199__ is maintained pure, so that your entire program is pure. If you need some effectful logic to run in a wrapping __INLINE_CODE_200__, you should consider using driver for that. Wrappers, just like any regular __INLINE_CODE_201__ function, can internally use __INLINE_CODE_202__ that are created from __INLINE_CODE_203__.
Wrappers can also add arbitrary __INLINE_CODE_204__ and could expect certain __INLINE_CODE_205__ to be returned from the Cycle program, depending on particular wrapper implementation.
Wrappers are usually created to encapsulate some non-business-logic stuff, you'd be normally describe anything related to the app itself in the "original" __INLINE_CODE_206__ function.
Most of __INLINE_CODE_207__ (aside from __INLINE_CODE_208__ HOC itself) is implemented with wrappers, that are created for each __INLINE_CODE_209__ed component automatically:
- __INLINE_CODE_210__ (to mess around with React component props)
- __INLINE_CODE_211__ (a gateway to React component lifecycle streams)
- __INLINE_CODE_212__ (to route the view events back from your wrapped React component aka view, and pass the events upstream, to parent components)
Sources
This is a Cycle.js program's input. __INLINE_CODE_213__ function shouldn't care whether something on the __INLINE_CODE_214__ is a __INLINE_CODE_215__ or some custom thing provided by one of the wrappers.
For __INLINE_CODE_216__ connected component, the __INLINE_CODE_217__ are __INLINE_CODE_218__ directly plus whatever __INLINE_CODE_219__ add to them.
Sinks
This is a Cycle.js program's output.
__INLINE_CODE_220__ invokes your __INLINE_CODE_221__ed component's __INLINE_CODE_222__ function, takes its __INLINE_CODE_223__ and subscribes the __INLINE_CODE_224__ passed from the parent component to these __INLINE_CODE_225__. Its how __INLINE_CODE_226__ is "attaching" the new component to a tree of Cycle programs.
If there's no parent component (thus, no __INLINE_CODE_227__) and your component is a "Root Cycle", then the sinks are handled to __INLINE_CODE_228__ to "run" as with a regular Cycle.js app.
React __INLINE_CODE_229__
The __INLINE_CODE_230__ed __INLINE_CODE_231__ component is essentially a view for the associated Cycle.js __INLINE_CODE_232__ function. And __INLINE_CODE_233__ encourages the view to be a "dumb" stateless function that takes __INLINE_CODE_234__ as an argument ad returns something of __INLINE_CODE_235__ type (i.e., __INLINE_CODE_236__s, __INLINE_CODE_237__, etc, whatever __INLINE_CODE_238__ component renders).
Note: The wrapped __INLINE_CODE_239__ shouldn't necessarily be a stateless functional component (SFC), you could have any __INLINE_CODE_240__ component wrapped, but its advisable to use functions for the most part as the whole point of __INLINE_CODE_241__ is to use __INLINE_CODE_242__ for anything that is not a view.
So, the __INLINE_CODE_243__ is the only communication channel from our Cycle.js part (the connected component's __INLINE_CODE_244__ function) to connected __INLINE_CODE_245__ component itself. We therefore need to pass the props from our Cycle program to the view (a __INLINE_CODE_246__ component) somehow.
This is where __INLINE_CODE_247__ comes into play and provides __INLINE_CODE_248__ source and sink for a __INLINE_CODE_249__ function. Its local to a component, so no other components will share the same __INLINE_CODE_250__ source or sink.
__INLINE_CODE_251__ sink
The __INLINE_CODE_252__ sink on __INLINE_CODE_253__ed is there to pass the props to the view:
function main(sources) {
return {
props: xs.of({ greetable: 'World' })
};
}
// Use the passed `greetable` prop
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
Remember, that the value you map to __INLINE_CODE_254__ sink should be an object of new props and that updated props will be merged with existing component __INLINE_CODE_255__ like:
const updatedProps = Object.assign({}, prevProps, newProps);
This means that you can easily overwrite any props you want and leave the others intact.
Also, note above that we're passing the __INLINE_CODE_256__ through an __INLINE_CODE_257__ stream and that might be synchronous (like in our example with a stream of static data that is sent immediately) but could also be asynchronous. In that case the value of some prop might be not yet available on the first render:
function main(sources) {
return {
props: xs.of({ greetable: 'World' }).compose(delay(3000))
};
}
// Trying to use the passed `greetable` prop
// But whooops, no `greetable` yet :(
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
So either provide a default value for that async prop's __INLINE_CODE_258__ or perform additional checks in the view, when rendering.
Note: When messing with the __INLINE_CODE_259__ in your __INLINE_CODE_260__, remember that any props that are passed from outside as an input to a __INLINE_CODE_261__ component, are also there (including callback functions, if there are any) and are also merged upon updates from outside (on __INLINE_CODE_262__).
Lets summarize: the __INLINE_CODE_263__ from the __INLINE_CODE_264__ sink are passed from the __INLINE_CODE_265__ function to the view (__INLINE_CODE_266__ component).
__INLINE_CODE_267__ source
Not only you'll need to provide the props to the view for rendering, but oftentimes you'd need your __INLINE_CODE_268__ to take part in transformations/calculations in the __INLINE_CODE_269__ function itself. For that reason, __INLINE_CODE_270__ provides the __INLINE_CODE_271__ source as well.
It allows you to have an __INLINE_CODE_272__ stream of your component's input __INLINE_CODE_273__. Note that the __INLINE_CODE_274__ is of type __INLINE_CODE_275__ and to take the actual stream of props, use its __INLINE_CODE_276__ attribute:
function main(sources) {
const name$ = sources.props.props$.map(props => props.name);
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
function MyComponent(props) {
return (
<div>
Hello, {props.name}! Your father is Mr. {props.father}.
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
...
// Somewhere else:
<MyConnectedComponent name="Luke Skywalker" father="Darth Vader" />
// renders:
//
// <div>
// Hello, LUKE SKYWALKER! Your father is Mr. Darth Vader.
// </div>
The new value on __INLINE_CODE_277__ is emitted every time __INLINE_CODE_278__ change. This could be overkill for the most cases, as you would only need just few props for your dataflow.
This is what __INLINE_CODE_279__ method for, it accepts the name of the nested prop (will return __INLINE_CODE_280__ if the stream value is not an object) and returns a stream of that prop only that is only got new value once this particular prop changes:
function main(sources) {
const name$ = sources.props.pluck('name');
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
The __INLINE_CODE_281__ed stream also performs shallow comparison and does __INLINE_CODE_282__ behind the scenes, so identical values don't cause a new value emitted on an __INLINE_CODE_283__.
Note that you don't need to always pull all props from your __INLINE_CODE_284__ function's __INLINE_CODE_285__ just for the sake of it to just forward to the __INLINE_CODE_286__ sink directly, you better only pick those you need in a dataflow with __INLINE_CODE_287__. The __INLINE_CODE_288__ passed from outside will be available on the view normally. You can overwrite them from inside your __INLINE_CODE_289__ though.
There's also __INLINE_CODE_290__ method that also accepts a nested prop name. The difference is that the result is not an __INLINE_CODE_291__ stream of prop values but the __INLINE_CODE_292__ instance itself that is scoped to that piece of __INLINE_CODE_293__:
function main(sources) {
const name$ = sources.props.pluck('name'); // Stream of strings
const nameSource = sources.props.select('someNestedObject'); // Scoped ReactPropsSource
const someNestedObjProperty$ = nameSource.pluck('someNestedObjectProperty');
...
}
This is useful when you want to __INLINE_CODE_294__ a stream of some sub-prop from deeper nested objects.
Note: Taking deep property stream this way isn't really nice currently as requires quite a bit of boilerplate - this part of API is already being thought out further and reconsidered.
Interactions
So far so good, but rendering the __INLINE_CODE_295__ isn't the only exciting thing we want to do with __INLINE_CODE_296__, right? We also want to handle events coming from the view(e.g., the DOM).
The way __INLINE_CODE_297__ handles events and nested components' callbacks is the biggest deviation from an idiomatic Cycle.js app using __INLINE_CODE_298__ driver. It refrains from a holistic "no handlers in the view" approach for a few reasons:
- We need something more in line with __INLINE_CODE_299__ as we're using React here
- We're not always handling DOM events but also need to handle some children components data/events passed back. We can't pass streams like in regular Cycle.js app, in __INLINE_CODE_300__ we do it via callbacks that are provided amongst children components' __INLINE_CODE_301__ and we need __INLINE_CODE_302__ed components to be compatible __INLINE_CODE_303__ components at all levels.
This is what "interactions" feature of __INLINE_CODE_304__ is for. It provides the unified interface for parent-child components communication and event handling that is aligned with a regular event/callback handling model on __INLINE_CODE_305__ side but also allows to access those events in form of __INLINE_CODE_306__ streams inside the __INLINE_CODE_307__ function.
Events triggering by the view
Every __INLINE_CODE_308__ component has some additional __INLINE_CODE_309__-specific props injected, so they are available on your view.
These are __INLINE_CODE_310__ which is a function that takes the __INLINE_CODE_311__ (a string) and optional __INLINE_CODE_312__ (of any type) and returns a function that will be a callback for your event or some child component prop function.
So, the signature is:
(interactionType, predefinedValue?) => (value) => void
Its supposed to be called imperatively in your view and is available as __INLINE_CODE_313__:
function MyComponent({ interact }) {
return (
<div>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
For the sake of being cleaner (and for better TypeScript support), __INLINE_CODE_314__ also leverages the ES2015 __INLINE_CODE_315__ feature to provide the object with dynamic properties, each representing an __INLINE_CODE_316__ with __INLINE_CODE_317__ dynamically defined after the function property name. Its available on __INLINE_CODE_318__ so we can rewrite the above snippet as:
function MyComponent({ interactions }) {
return (
<div>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
In the example above, the __INLINE_CODE_319__ function receives the click __INLINE_CODE_320__ as an argument and that is what you'll get in your __INLINE_CODE_321__ (next section).
While cleaner, it doesn't allow for the __INLINE_CODE_322__ to be set, so if you need some particular value to be emitted by the associated stream in your __INLINE_CODE_323__, you can either use __INLINE_CODE_324__ or create an inline function:
// Assuming you have `id` available, e.g.
// when looping/mapping over the collection of items
<button
type="button"
className="delete-btn"
onClick={interact('delete', id)}
>
Click me!
</button>
// OR
<button
type="button"
className="delete-btn"
onClick={() => interactions.delete(id)}
>
Click me!
</button>
View events handling with __INLINE_CODE_325__ source
There's internal __INLINE_CODE_326__ in __INLINE_CODE_327__ and it provides the __INLINE_CODE_328__ source for our __INLINE_CODE_329__ function. The __INLINE_CODE_330__ source is of type __INLINE_CODE_331__, which has the __INLINE_CODE_332__ method, which we can use to obtain interaction streams:
function main(sources) {
const buttonClick$ = sources.interactions.select('buttonClick');
// Do something with a buttonClick$, e.g.
// map it to the click coordinates props
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interact, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
The streams are __INLINE_CODE_333__ed by the same __INLINE_CODE_334__ string you provided to the __INLINE_CODE_335__ function on your view.
Similar to how __INLINE_CODE_336__ object with dynamic function-props is available on your view, __INLINE_CODE_337__ is a ES2015 __INLINE_CODE_338__ and it maintains the dynamic streams right on itself (just don't use __INLINE_CODE_339__ as its reserved method name here here):
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interactions, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
Note: If you try to get the non-existing stream, it will still work, this thing is dynamic, so the stream will be created nevertheless but will just never emit values. Be careful and look out for typos! Using TypeScript is highly recommended to avoid typos here.
Upstream interactions with __INLINE_CODE_340__ sink (calling callbacks on __INLINE_CODE_341__).
Now that we can handle the streams of view events and values from the children components, we would also need to somehow call the callbacks that are passed among our __INLINE_CODE_342__.
First, nothing prevents from following the common React way and call those directly from the view:
function MyComponent({ onButtonClick }) {
return (
<button type="button" onClick={onButtonClick}>
Click me!
</button>
);
}
// Somewhere else:
doSomething = (event) => { console.log('clicked!'); };
...
<MyComponent onButtonClick={doSomething} />
This is fine if you don't need that value's stream in your dataflow. This approach bypasses your Cycle.js program's __INLINE_CODE_343__ completely and doesn't make it impure by otherwise possibly passing an effectful function as __INLINE_CODE_344__. There's even no need to __INLINE_CODE_345__ your component in the above example.
Keep in mind that if __INLINE_CODE_346__ is optional there, you will also need to check whether that is defined before providing that as a callback (use __INLINE_CODE_347__ or TypeScript to mark it as either required or optional).
Oftentimes you will need to call some callback that lives on the __INLINE_CODE_348__ though, and this is what __INLINE_CODE_349__ sink provided by __INLINE_CODE_350__ does:
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
const coords$ = buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
);
// We both update the `props` (to render it in this component)
// and pass upstream
return {
props: coords$,
interactions: xs.of({
onCoordsChange: coords$
})
};
}
function MyComponent({ interactions }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
handleCoordsChange = (coords) => {
console.log('New coordinates received: ', coords);
};
...
<ConnectedComponent onCoordsChange={handleCoordsChange} />
// The new coords will be rendered by the component
// and the console will show the "New coordinates received"
// with new coords too.
Once the appropriate stream emits a value, that callback is called. It will also check if the callback was passed in the first place to skip the call attempt if no callback with a given name is passed among the __INLINE_CODE_351__.
Important: Note that __INLINE_CODE_352__ sink expects a stream of object whose property names correspond to callback names on __INLINE_CODE_353__ and property values are themselves streams too - whatever values those streams emit, will be passed as an argument to the callback.
So far so good, but we know that our __INLINE_CODE_354__ is a wrapper and not a driver, so what if one passed the effectful function as a callback, e.g., __INLINE_CODE_355__ passed as __INLINE_CODE_356__ above does a __INLINE_CODE_357__ internally - that is a side effect, right?
Well, this is why the function is not invoked directly, or inside the wrapper (since the wrapper is the part of the pure land of our __INLINE_CODE_358__s), its using the __INLINE_CODE_359__ driver internally. More on this below in a section about Side-effect control, just keep in mind that __INLINE_CODE_360__ and __INLINE_CODE_361__ should be pure and __INLINE_CODE_362__ follows that requirement.
React component lifecycle streams
You're probably not going to need __INLINE_CODE_363__ component lifecycle hooks often inside your __INLINE_CODE_364__, but still - they are provided via special __INLINE_CODE_365__. It provides the __INLINE_CODE_366__ source that is available on each __INLINE_CODE_367__ed __INLINE_CODE_368__ sources.
The lifecycle events are provided as __INLINE_CODE_369__ streams and their names mostly mirror the React lifecycle methods, but shortened to remove some redundant __INLINE_CODE_370__ prefixes:
React lifecycle method
__INLINE_CODE_371__ source property
Stream value
__INLINE_CODE_372__
__INLINE_CODE_373__
__INLINE_CODE_374__
__INLINE_CODE_375__
__INLINE_CODE_376__
__INLINE_CODE_377__
__INLINE_CODE_378__
__INLINE_CODE_379__
__INLINE_CODE_380__
__INLINE_CODE_381__
__INLINE_CODE_382__
__INLINE_CODE_383__
__INLINE_CODE_384__
__INLINE_CODE_385__
__INLINE_CODE_386__
__INLINE_CODE_387__
__INLINE_CODE_388__
__INLINE_CODE_389__
__INLINE_CODE_390__
__INLINE_CODE_391__
Error data
The "error data" on the __INLINE_CODE_392__ is an object of this shape:
{
error: Error;
errorInfo: ErrorInfo;
}
These are basically React __INLINE_CODE_393__ arguments squashed into a single object.
Also, note that __INLINE_CODE_394__ hook is not present there, since that is not really an event and affects how your component is rendered. __INLINE_CODE_395__ would be providing its own custom internal __INLINE_CODE_396__ instead for optimisation purposes and you could implement it on your wrapped component anyway.
Note: As well, no lifecycle events are passing anything related to React component __INLINE_CODE_397__, only taking __INLINE_CODE_398__ into account instead. This is because the __INLINE_CODE_399__ is meant to be managed differently when Cycle.js is in use. You can still use that for wrapped component but its out of play on the component generated by __INLINE_CODE_400__.
A quick example:
function main(sources) {
const willUnmount$ = { sources.lifecycle };
// Do something on unmounting, e.g. report that to a console
// via some hypothetical `logger` driver
return {
logger: willUnmount$.mapTo(`I'll be unmounted soon!`)
};
}
Side-effect control and __INLINE_CODE_401__ driver
Side-effect control is where Cycle.js really shines (not only implementation, it applies to the "cycle pattern" behind Cycle.js in the first place).
In order to keep the __INLINE_CODE_402__ part of __INLINE_CODE_403__ed component pure, we need to make sure that any potentially effectful function is not run inside our __INLINE_CODE_404__ directly.
This is what __INLINE_CODE_405__ driver is responsible for. It allows to only describe what function you want to call and with what arguments, then delegate that to a __INLINE_CODE_406__ which is supposed to run any impure functions outside of the app itself.
The way it works is very similar to that of __INLINE_CODE_407__ driver from __INLINE_CODE_408__ package where you don't actually run the request inside your __INLINE_CODE_409__ but just describe it and pass to __INLINE_CODE_410__ driver to handle. __INLINE_CODE_411__ driver is just a bit more generic and operates on functions.
__INLINE_CODE_412__ uses __INLINE_CODE_413__ driver internally (to run effects for upstream __INLINE_CODE_414__), but it is also available for you on any connected __INLINE_CODE_415__ as __INLINE_CODE_416__ sink automatically (i.e., without explicit configuration on a root cycle):
function log() {
console.log(`I'm effectful!`);
}
function saveToLocalStorage(message) {
localStorage.setItem('message', message);
}
function main(sources) {
const someCallbackFn$ = sources.props.pluck('someCallbackFunction');
return {
fnCallEffect: xs.merge(
xs.of({ fn: log }),
xs.of({ fn: saveToLocalStorage, args: ['some message to save'] }),
// Call the `props` callback function directly, this is how
// `interactions` sink works behind the scenes:
someCallbackFn$.map(fn => ({ fn }))
)
};
}
Isolation
Isolation is Cycle.js way of making the component's __INLINE_CODE_417__ and __INLINE_CODE_418__ isolated from influence of other components.
Make sure to carefully read more about Cycle.js isolation here. It might take a bit to get used to an idea.
To reiterate: the concept of isolation is important for Cycle.js apps. Its a little bit different for __INLINE_CODE_419__ because most things are scoped to a particular __INLINE_CODE_420__ed component already, like components have "local" __INLINE_CODE_421__, __INLINE_CODE_422__ and __INLINE_CODE_423__ sources and sinks and __INLINE_CODE_424__ components are also self-contained already.
There are though __INLINE_CODE_425__/__INLINE_CODE_426__ that will need the isolation feature, like __INLINE_CODE_427__ driver or __INLINE_CODE_428__ wrapper (a state management solution for Cycle.js apps). So, isolation makes sense for shared __INLINE_CODE_429__/__INLINE_CODE_430__, those you pass to __INLINE_CODE_431__ function (by defining the __INLINE_CODE_432__/__INLINE_CODE_433__ options on root __INLINE_CODE_434__) and that will be available across the entire tree of Cycle programs.
Note: The isolation works by scoping the source streams to particular component and a kind of unscoping of the sink streams before connecting to parent's sinks.
Also note, that each driver/wrapper decides on its own how its sources/sinks should be isolated.
In Cycle.js, you use the __INLINE_CODE_435__ function from __INLINE_CODE_436__ package directly, as you're just invoking the children __INLINE_CODE_437__ functions from your current component's __INLINE_CODE_438__ function:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// Do something with childSinks, combine somehow, etc
return {
...
};
}
__INLINE_CODE_439__ function has this simple signature:
isolate :: (MainFn, scope) => MainFn
It takes the __INLINE_CODE_440__ function and an optional __INLINE_CODE_441__ and returns an isolated __INLINE_CODE_442__ function.
__INLINE_CODE_443__ does also use the __INLINE_CODE_444__ package internally, but you can't call __INLINE_CODE_445__ directly on some __INLINE_CODE_446__ function. You'd never call one component's __INLINE_CODE_447__ from the other component's __INLINE_CODE_448__ directly with __INLINE_CODE_449__.
Naive explicit isolation (conceptually incorrect)
Just to illustrate the issue, there's an attempt to isolate the component's __INLINE_CODE_450__ sources, so that HTTP requests initiated from that components are scoped to it (and are unavailable to siblings).
Since we know that __INLINE_CODE_451__ returns a __INLINE_CODE_452__ function and __INLINE_CODE_453__ takes the __INLINE_CODE_454__ function as an argument, lets try to feed it an isolated version of it:
function main(sources) {
...
}
function MyComponent() {
...
}
const isolatedMain = isolate(main, { HTTP: 'myComponent' });
const ConnectedComponent = cycleConnect(isolatedMain)(MyComponent);
While this will won't raise any errors (because this is valid code after all), this approach brings a subtle issue.
This will isolate things for this particular components but not their children components, so in this case, children (connected childred __INLINE_CODE_455__ components) will have no __INLINE_CODE_456__ isolation scope for __INLINE_CODE_457__ channel.
This might seems like a no-issue, because hey, you can define different scopes for the children components right? Well, while this is technically possible, its conceptually incorrect and it will be completely broken in some cases, e.g., with __INLINE_CODE_458__ where the deeper you go - the "more filtered" source streams you get.
Important! The above note about it being "conceptually incorrect" refers to the way __INLINE_CODE_459__ maintains the tree of Cycle.js programs internally. It tries to make the entire app structure look the same way as you'd manually write a Cycle.js app. So, when you __INLINE_CODE_460__ at some level, then if inside your __INLINE_CODE_461__ you also call some __INLINE_CODE_462__ happens, then __INLINE_CODE_463__ gets isolated sources no matter what.
__INLINE_CODE_464__ option for __INLINE_CODE_465__
The first way to correctly isolate __INLINE_CODE_466__ed component is to use the __INLINE_CODE_467__ option for __INLINE_CODE_468__ itself, so the above can be rewritten as:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main, {
isolate: { HTTP: 'myComponent' }
})(MyComponent);
This will work perfectly, with all children __INLINE_CODE_469__ed components having __INLINE_CODE_470__ namespace applied to __INLINE_CODE_471__ sources/sinks in their __INLINE_CODE_472__ functions.
The __INLINE_CODE_473__ option accepts a __INLINE_CODE_474__ or the __INLINE_CODE_475__ and in that case its passed directly to __INLINE_CODE_476__ package's __INLINE_CODE_477__ as a __INLINE_CODE_478__ argument. So, check out the __INLINE_CODE_479__ function API reference.
__INLINE_CODE_480__ also makes it accept a function that takes component __INLINE_CODE_481__ as an argument and should return the scope of __INLINE_CODE_482__ or __INLINE_CODE_483__ type that is then also passed to __INLINE_CODE_484__ package's __INLINE_CODE_485__. This allows for some dynamic isolation scopes to be set based on the React component's initial props.
Important: Despite the __INLINE_CODE_486__ is provided as an option to __INLINE_CODE_487__ when defining the connected component, the actual isolation will only happen when component instance is created. This is why its possible to dynamically resolve the __INLINE_CODE_488__ based on the component __INLINE_CODE_489__ in __INLINE_CODE_490__.
__INLINE_CODE_491__ prop
Despite the __INLINE_CODE_492__ option will work nicely for some cases, there's a pattern in how it is utilized in pure Cycle.js programs. Lets take a look at pure Cycle.js example again:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// ...
return {
...
};
}
The important part is that in Cycle.js app its not a child who decides how to isolate itself but __INLINE_CODE_493__ is provided from the parent instead.
Most of the time, we want to maintain the same approach with __INLINE_CODE_494__. The advantage is that component in this case is totally agnostic from the provided scope, it doesn't even care whether its isolated or not at all - all it knows is that it expects certain __INLINE_CODE_495__ and returns certain __INLINE_CODE_496__. This makes the component reusable.
This is especially important with __INLINE_CODE_497__, where you want to control where your child components "attach" to the state.
In other words, the parent decides how to compose children components, not the other way around, because otherwise the child would need to have an intimate knowledge of something that really belongs to parent and thus not being reusable and breaking encapsulation.
For this reason, every __INLINE_CODE_498__ed React component accepts an __INLINE_CODE_499__ prop, so you can use that just like any other prop:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
// Isolate everything
<ConnectedComponent isolate="myComponent" />
// Isolate HTTP only
<ConnectedComponent isolate={{ HTTP: 'myComponent' }} />
<ConnectedComponent isolate={{ HTTP: 'myComponent_2' }} />
In contrast with __INLINE_CODE_500__ option, the __INLINE_CODE_501__ prop can only accept __INLINE_CODE_502__ or __INLINE_CODE_503__ that is directly passed to __INLINE_CODE_504__ function from __INLINE_CODE_505__, thus maintaining full parity with pure Cycle.js approach. Indeed, having a function that accepts initial __INLINE_CODE_506__ wouldn't make much sense because you're anyway providing props that could be different for different component instances when rendering that component.
__INLINE_CODE_507__ helper component
Since isolation in __INLINE_CODE_508__ applies to both "current" __INLINE_CODE_509__ed component and all its __INLINE_CODE_510__ed children, we might sometimes need to define some connected component to only be a common parent for some children, which must provide isolation but wouldn't contain any logic.
This is what a helper __INLINE_CODE_511__ component for. It accepts the __INLINE_CODE_512__ prop (that is directly passed as a __INLINE_CODE_513__ argument to __INLINE_CODE_514__ function of __INLINE_CODE_515__):
<Isolator scope={{ HTTP: 'someNamespace' }}>
<ConnectedComponent someProps="1" />
<ConnectedComponent someProps="2" />
<ConnectedComponent someProps="3" />
</Isolator>
This assists the isolation composition at view level.
Extra: State management with __INLINE_CODE_516__
__INLINE_CODE_517__ is the idiomatic Cycle.js fractal state management solution. It is tailored for Cycle.js apps but since __INLINE_CODE_518__ is using the real Cycle.js, it stays fully compatible here as well.
Using __INLINE_CODE_519__ with __INLINE_CODE_520__ is optional, since you might be using different lib (e.g., __INLINE_CODE_521__) for that.
To start, just read the onionify docs and install the package:
$ npm install --save cycle-onionify
and pass that with __INLINE_CODE_522__ array to a __INLINE_CODE_523__ connected component (e.g., Root Cycle):
// RootCycle.js
import { run } from '@cycle/run';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
wrappers
})();
export default RootCycle;
Now every __INLINE_CODE_524__ed component that is a child of the above-mentioned __INLINE_CODE_525__ in React component tree got the __INLINE_CODE_526__ source and sink.
Important: Notice that you're all set - its purely a Cycle.js land. __INLINE_CODE_527__ doesn't provide any additional functionality for __INLINE_CODE_528__. Instead, it provides few helpers for isolation and working with collections on the React side.
All the exportables related to __INLINE_CODE_529__ reside in the extra package: __INLINE_CODE_530__. It is installed along with the main lib but is not __INLINE_CODE_531__d/__INLINE_CODE_532__ed by default, so won't be bundled (by Webpack, Rollup, etc) unless you're using it.
This extra sub-package contains few things:
- __INLINE_CODE_533__ helper component
- __INLINE_CODE_534__ helper component
- __INLINE_CODE_535__ helper factory function
Read ahead for details on each.
In __INLINE_CODE_536__, the state/reducers layer composition is done via isolation, so the first thing here is __INLINE_CODE_537__:
__INLINE_CODE_538__ helper component
This is pretty much like __INLINE_CODE_539__ but is here to specifically isolate __INLINE_CODE_540__ sources/sinks only.
So, the:
import { StateIsolator } from 'react-cycle-connect/lib/extra/onionify';
// ...
<StateIsolator lens="someStateProperty">
<NestedComponent />
</StateIsolator>
is somewhat a convenient shortcut to:
import { Isolator } from 'react-cycle-connect';
// ...
<Isolator scope={{ onion: 'someStateProperty', '*': null }}>
<NestedComponent />
</Isolator>
Notice how __INLINE_CODE_541__'s prop is explicitly named __INLINE_CODE_542__. It just implies that oftentimes you'll be using "lens" to isolate the state. Consider a string just a shortcut to a more elaborate __INLINE_CODE_543__ object.
In the example above, the __INLINE_CODE_544__'s __INLINE_CODE_545__ function __INLINE_CODE_546__ will represent only that "someStateProperty" piece of state. Pretty much how you'd __INLINE_CODE_547__ the __INLINE_CODE_548__ channel in an idiomatic Cycle.js app.
There's also a __INLINE_CODE_549__ property, so that you can change the name of the source/sink inside your __INLINE_CODE_550__ to something else. Thats to be on par with __INLINE_CODE_551__ features, where you can also change that name.
Handling collection state manually
Managing collections of components in pure Cycle.js with __INLINE_CODE_552__ is a kind of a recognized challenge. This is why __INLINE_CODE_553__ ended up providing __INLINE_CODE_554__ helper function - see their docs for more detail.
While __INLINE_CODE_555__ also has a __INLINE_CODE_556__ helper component that is very similar to __INLINE_CODE_557__ __INLINE_CODE_558__ function, lets first try to pretend we don't have it and imagine how'd we handle the state without it.
Note: Nothing prevents you from skipping right to a description of how __INLINE_CODE_559__ works, but it might be useful to understand the rationale behind it, so reading this section is recommended.
Handling collections is different with React than in Cycle.js, e.g., you can always just render React components in a loop or __INLINE_CODE_560__ an array of data to an array of components and React will render that.
When you want to render some __INLINE_CODE_561__ data from your state, its easy without any helpers:
:
{
list: [
{ id: 1, name: 'Obi-Wan Kenobi', kills: 100 },
{ id: 2, name: 'Luke Skywalker', kills: 25 },
{ id: 3, name: 'Darth Vader', kills: 9000 }
]
}
*/
// ListItem.jsx
function ListItem({ itemData }) {
return (
<li>
{itemData.name} (ID: {itemData.id}, kills: {itemData.kills})
</li>
);
}
// List.jsx
function main(sources) {
// Just pass as `props` directly
return {
props: sources.onion.state$
};
}
function List({ list }) {
return (
<ul>
{list.map(item => (
<ListItem key={item.id} itemData={item} />
))}
</ul>
);
}
const ConnectedList = cycleConnect(main)(List);
This keeps your __INLINE_CODE_562__ component just a "dumb" view, there's no even need of connecting it to the Cycle tree. So far so good.
Then you want to add more item-level capabilities, like editing an item (incrementing hero __INLINE_CODE_563__ in our case, for simplicity) or deleting an item. You can still manage the state in the __INLINE_CODE_564__ component, act upon some callbacks for particular item and only stream reducers from inside __INLINE_CODE_565__ component's __INLINE_CODE_566__:
/*
Assume you have this value on your `List` component `stateFork
- updated to more recent version of cycle, including @cycle/state
- updated to React 16.9 api, including new lifecycles and context api
- merged onionify separate lib so import is __INLINE_CODE_0__
- added tests
- published at @iilab/react-cycle-connect
react-cycle-connect
An experimental library that allows using Cycle.js as a reactive dataflow framework for React.
Written in TypeScript.
This little thing allows to define your app's view layer using regular React components, but implement the actual logic behind it using pure functional reactive approach, taking advantage of Cycle.js' Model-View-Intent pattern and its exceptional side effect control.
This isn't a custom implementation of Cycle pattern for __INLINE_CODE_1__ but a link to real Cycle.js instead, so any Cycle.js drivers and wrappers can be applied (e.g., __INLINE_CODE_2__ for state management). Also, for this reason, some knowledge about how __INLINE_CODE_3__ works is assumed.
Note: This is an experimental lib and currently works with __INLINE_CODE_4__ and __INLINE_CODE_5__ only. If all goes good, the support for __INLINE_CODE_6__/__INLINE_CODE_7__ (instead of __INLINE_CODE_8__) and for __INLINE_CODE_9__/__INLINE_CODE_10__ (instead of __INLINE_CODE_11__) is anticipated. Nothing prevents from having __INLINE_CODE_12__ covered too.
UPDATE (MAR 2018): Some internals overhaul is pending and it's currently waiting for Cycle.js to settle on hybrid push-pull reactivity and have internals rewritten to use Callbags instead of __INLINE_CODE_13__. So, __INLINE_CODE_14__ is going to be using Callbags by default too.
Any feedback is highly appreciated!
Table of Contents
- react-cycle-connect
- Table of Contents
- Shup up and show me the code
- Installation
- Motivation
- How it works (architecture)
- Guide
- API reference
- Alternatives / Competitors
- FAQ
- Roadmap
Shup up and show me the code
Cycle.js's counter example ported over:
import React from 'react';
import ReactDOM from 'react-dom';
import xs from 'xstream';
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
// Your Cycle.js `main` function
function main(sources) {
const action$ = xs.merge(
sources.interactions.decrement.map(ev => -1),
sources.interactions.increment.map(ev => +1)
);
const count$ = action$.fold((acc, x) => acc + x, 0);
return {
props: count$.map(count => ({ count }))
};
}
// Your React component
function App({ interactions, count }) {
return (
<div>
<button onClick={interactions.decrement}>Decrement</button>
<button onClick={interactions.increment}>Increment</button>
<p>Counter: {count}</p>
</div>
);
}
// Some connection magic
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
// Render normally as it's a regular React app
ReactDOM.render(
<ConnectedApp />,
document.getElementById('root')
);
Also - there's official TodoMVC example with __INLINE_CODE_33__ for state management.
Installation
Using npm:
$ npm install --save react-cycle-connect
Using yarn:
$ yarn add react-cycle-connect
Motivation
The motivation to create this lib was based off the need to retain the React-style composability of the view layer using regular React components, reuse existing React components, make it (the lib) easily/incrementally adoptable in an ongoing React project, but also take advantage of the Cycle.js' MVI pattern and side effect control.
At the same time, while the most valuable thing from __INLINE_CODE_34__ is the pattern itself, there are numerous available libs/plugins for Cycle.js to take advantage of, letting us take the best of both worlds.
This way, you can still use anything from React stack (e.g., __INLINE_CODE_35__ to compose your routed app screens), but describe the interactions as a dataflow, with pure functions and reactive streams. Most of React components are going to be "dumb" (and also stateless functional aka SFC) since all the backing logic and cross-component communication would be handled by a tree of __INLINE_CODE_36__ programs.
Why not just use Cycle.js alone?
That is what I'd recommend considering in the first place. Its fast, its cool, the __INLINE_CODE_37__ (virtual DOM rendering engine behind Cycle's DOM driver) is goddamn fast and reliable. If you don't intend to use __INLINE_CODE_38__ components in any way, don't even get bothered. The __INLINE_CODE_39__ is more about "introducing Cycle.js to __INLINE_CODE_40__", not the other way around.
Is it a "Cycle.js in React" or "React in Cycle.js"?
More of a "Cycle.js in React". The app is bootstrapped by rendering a React component to the DOM, view is composed the React way. The root Cycle.js program is being __INLINE_CODE_41__ by rendering a root __INLINE_CODE_42__ed component. Though, it doesn't prescribe how you write your Cycle.js programs, and aside from the DOM generation part, its just Cycle.js all the way.
How it works (architecture)
Every __INLINE_CODE_43__ed component got a real Cycle.js program "attached" to it. The boundary between the two is __INLINE_CODE_44__. From your Cycle.js program standpoint, __INLINE_CODE_45__ is a view. Cycle.js program can update __INLINE_CODE_46__ that React component will then render.
A bunch of nested React components in your app represent a "tree of React components". __INLINE_CODE_47__ maintains a "tree of Cycle.js programs" that map to __INLINE_CODE_48__ed components and takes advantage of React's context feature to provide links between the nodes. Each connected component provides scoped links to the children nodes .
New "Cycle nodes" are attached lazily, upon associated React component initialization (this is where React controls how Cycle programs are run). The above-mentioned "link" is just an object on React __INLINE_CODE_49__ that brings couple properties:
- __INLINE_CODE_50__ for the child connected component's __INLINE_CODE_51__ function; and
- __INLINE_CODE_52__ to subscribe to that component's __INLINE_CODE_53__ function __INLINE_CODE_54__.
This is where something unusual for the Cycle.js happens - we subscribe to and unsubscribe from connected component's __INLINE_CODE_55__ function __INLINE_CODE_56__ based on React component's lifetime hooks. In Cycle.js, you normally just run the app at startup and let it run as an ongoing process. But this is where __INLINE_CODE_57__ performs the sync between the two worlds.
Overall, aside from using React __INLINE_CODE_58__ (which is just an implementation detail) and some intense wrapping on the __INLINE_CODE_59__ side to make laziness possible, __INLINE_CODE_60__ tries to represent the entire tree of Cycle.js programs as close as possible to what you'd end up with by writing idiomatic Cycle.js with explicit calling of nested component's __INLINE_CODE_61__ functions from the parent component __INLINE_CODE_62__ function. The result of children components is always __INLINE_CODE_63__d.
Aside from passing the __INLINE_CODE_64__ to children components, __INLINE_CODE_65__ also explicitly applies some internal wrappers to integrate with React component: __INLINE_CODE_66__, __INLINE_CODE_67__ and __INLINE_CODE_68__, that are scoped to a connected component's Cycle.js program only. It also explicitly runs the internal __INLINE_CODE_69__ driver for the __INLINE_CODE_70__ nodes by default - this is necessary for the __INLINE_CODE_71__ sink to work (because its supposed to run callbacks passed via __INLINE_CODE_72__ and that might be an effectful function).
Note that the entire internal "tree" of Cycle.js programs is maintained pure, including internal component-scoped wrappers, just as you would do with regular Cycle.js program.
Differences from idiomatic Cycle.js usage
Note: there's a difference in how'd you write the __INLINE_CODE_73__/__INLINE_CODE_74__ app vs pure __INLINE_CODE_75__ app. There's no misuse of Cycle.js per se, we just don't use it for rendering with __INLINE_CODE_76__ - and that applies certain implications to the approach.
__INLINE_CODE_77__ and __INLINE_CODE_78__, while both allow to create user interfaces, imply different approaches to the app architecture. The most important part here is that they are conceptually different when it comes to generating the VDOM.
In order to combine the two approaches nicely, there are certain deviations from how'd you write the __INLINE_CODE_79__-only app to allow for more optimal React DX.
The main difference from a pure __INLINE_CODE_80__ way is that its not a Cycle program that technically generates VDOM - instead, there's React component to represent a piece of UI. __INLINE_CODE_81__ approach to view/__INLINE_CODE_82__ event handling is rather purist - view only defines the markup and nothing else (i.e., user interaction/event handlers are defined inside Cycle program itself and not as some view component __INLINE_CODE_83__/etc attribute). That is not a crazy random opinion but instead a very well thought and weighted out decision by Cycle.js team. __INLINE_CODE_84__ takes a different approach, which isn't that strict and is more in line with React, with callbacks amongst __INLINE_CODE_85__ and all that stuff. It enables the events/component callbacks handling via interactions.
The view composition is obviously completely different. In __INLINE_CODE_86__ you run each component's __INLINE_CODE_87__ function that (possibly) generates a VDOM node and then explicitly combine the pieces of markup into a single VDOM stream that is then given to a DOM driver. With __INLINE_CODE_88__ you compose the view layer with React components normally. You can just connect those to Cycle.js program to "outsource" the actual logic to it.
Guide
This assumes the preliminary knowledge of both __INLINE_CODE_89__ and __INLINE_CODE_90__ (and __INLINE_CODE_91__). It doesn't provide much of the insight on how these work, but is instead focused on how __INLINE_CODE_92__ combines the two.
Basics
The result of __INLINE_CODE_93__ call is a HOC that optionally takes a React component (similar to that __INLINE_CODE_94__ function of __INLINE_CODE_95__) and returns a component that is augmented with backing Cycle.js program. This Cycle.js program is internally wrapped into a "Cycle Node" that, when initialized, is connected to a tree of Cycle.js programs. "Cycle Nodes" of every __INLINE_CODE_96__ed components are linked together via React __INLINE_CODE_97__.
Note, though, that there are no direct references from one component's Cycle program to another component's Cycle program, i.e., you're not invoking one __INLINE_CODE_98__ function from the other (this is perfectly possible though, but since we're not using __INLINE_CODE_99__ here, it would only handle non-DOM stuff). Instead, __INLINE_CODE_100__ handles the linking behind the scenes, so the "sibling" Cycle programs are fed with the same __INLINE_CODE_101__ and subscribe their __INLINE_CODE_102__ to the same __INLINE_CODE_103__.
There's essential concept of isolation in Cycle.js, so every __INLINE_CODE_104__ed component accepts an __INLINE_CODE_105__ prop, that is directly passed to __INLINE_CODE_106__ function from __INLINE_CODE_107__ package. Read more about Cycle.js isolation here.
Root Cycle
Every __INLINE_CODE_108__ed component provides a __INLINE_CODE_109__ object to its children through React __INLINE_CODE_110__ and also expects that __INLINE_CODE_111__ from some parent component (not necessarily immediate parent).
That link object has the following shape:
{
// Sources to provide to our Cycle.js program's `main` function as an argument
sources: { ... },
// sinkProxies to subscribe to our Cycle program's `main` function sinks
sinkProxies: { ... }
}
You shouldn't be working with that directly but its important that something needs to provide the top-level __INLINE_CODE_112__ and __INLINE_CODE_113__ in the first place. This is what __INLINE_CODE_114__ option of cycle connect for.
Also, the __INLINE_CODE_115__ed components' programs (__INLINE_CODE_116__ functions) are lazily attached to already running upper-level Cycle.js program, so the __INLINE_CODE_117__ program has to be __INLINE_CODE_118__.
So, root __INLINE_CODE_119__, aside from __INLINE_CODE_120__ option, also needs __INLINE_CODE_121__ defined, so that it knows how to run your top-level Cycle.js program. This would normally be just a __INLINE_CODE_122__ function from __INLINE_CODE_123__ package (or __INLINE_CODE_124__, or __INLINE_CODE_125__, depending on the reactive streams library in use).
You could just __INLINE_CODE_126__ your top-level __INLINE_CODE_127__ component like we did in the first example:
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
...
function main(sources) {
...
}
function App() {
return (
// Some JSX
...
);
}
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
Though this will work, its advisable to create a specialized __INLINE_CODE_128__ component just for this purpose:
// RootCycle.js
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
function main(sources) {
...
}
export const RootCycle = cycleConnect(main, {
root: true,
runFn: run
})(); // No component to wrap? Its okay, read ahead
export default RootCycle;
The top-level Cycle sources are actually __INLINE_CODE_129__ and __INLINE_CODE_130__, so root Cycle is also in charge of providing these __INLINE_CODE_131__ and __INLINE_CODE_132__ to any Cycle.js program down the tree, as __INLINE_CODE_133__.
__INLINE_CODE_134__ option is the object with keys defining properties of the __INLINE_CODE_135__ object, identical to how you'd do that in a regular Cycle.js program.
__INLINE_CODE_136__ option accepts an array of wrappers (order might matter there, so its a user-land option).
Also, the __INLINE_CODE_137__ function is optional (because you might need a __INLINE_CODE_138__ to be a __INLINE_CODE_139__ or for __INLINE_CODE_140__ only), so you can skip it, unless you have some logic to implement there.
Note that the __INLINE_CODE_141__ can be passed to any __INLINE_CODE_142__, even non-root, but __INLINE_CODE_143__ option is only valid for __INLINE_CODE_144__ Cycle and is ignored otherwise.
// RootCycle.js
import { run } from '@cycle/run';
import { makeHTTPDriver } from '@cycle/http';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const drivers = { HTTP: makeHTTPDriver() };
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
export default RootCycle;
__INLINE_CODE_145__ component is basically in charge of bootstrapping the "Cycle" part of your app here.
Then just wrap your __INLINE_CODE_146__ with it and e.g., render with __INLINE_CODE_147__:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import RootCycle from './RootCycle';
ReactDOM.render(
(
<RootCycle>
<App />
</RootCycle>
),
document.getElementById('root')
);
__INLINE_CODE_148__ option
You might have noticed in axamples above that we're not providing any component to a __INLINE_CODE_149__ HOC, leaving empty braces instead:
const ConnectedComponent = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
Well, that is not a mistake - the wrapped component is optional. When a "component to wrap" is provided to __INLINE_CODE_150__, then if we render the connected component, the __INLINE_CODE_151__ will also render __INLINE_CODE_152__ internally.
Thats all good and works (and what you'll be using most of the time anyway), but sometimes you just want to keep your React Element tree a bit cleaner when the wrapped component would be a kind of noop anyway (just forwarding __INLINE_CODE_153__ and nothing else), or component is some technical/lower-level like __INLINE_CODE_154__, or have some special rendering behavior.
So, we want to avoid seeing this in React Developer Tools:
<cycleConnect(SomeComponent)>
<SomeComponent>
...
</SomeComponent>
</cycleConnect(SomeComponent)>
You can use __INLINE_CODE_155__ option for that. It accepts the function with this signature:
(props) => ReactNode | null
If no component is provided for wrapping, nor __INLINE_CODE_156__ option is defined, the __INLINE_CODE_157__ option defaults to __INLINE_CODE_158__ function, so its either forwarding __INLINE_CODE_159__ if any or renders nothing.
If both __INLINE_CODE_160__ option is provided and component-to-wrap is passed, then __INLINE_CODE_161__ takes the precedence.
Note: Why not use the __INLINE_CODE_162__ option in place of wrapped component all the time? Well, its technically possible, espeically if your React component is a stateless functional component anyway.
But regular HOC wrapping is just common technique in React world and is nicely composable when you have other HOCs than __INLINE_CODE_163__.
And is cleaner too - you might be prototyping your app as a set of __INLINE_CODE_164__ components first, without even involving __INLINE_CODE_165__, then "connect" them all at once, or introduce __INLINE_CODE_166__ incrementally. Whatever :)
__INLINE_CODE_167__ option
Your component's __INLINE_CODE_168__ is by default automatically assigned to the following (depending on whether the wrapped component is provided, or __INLINE_CODE_169__ option, or neither:
- __INLINE_CODE_170__ when WrappedComponent is passed
- __INLINE_CODE_171__ when __INLINE_CODE_172__ prop is defined (and is truthy)
- __INLINE_CODE_173__ otherwise
You can override that like with any regular React component:
const ConnectedComponent = cycleConnect({
...
)(Component);
ConnectedComponent.displayName = 'ConnectedComponent';
But there's also a __INLINE_CODE_174__ option to set the generated (connected) component __INLINE_CODE_175__, which looks a bit cleaner:
const ConnectedComponent = cycleConnect({
...
displayName: 'ConnectedComponent'
)(Component);
Inside the Cycle
__INLINE_CODE_176__ really assumes that you know how to use Cycle.js. Its really a very simple yet very elegant pattern, so make sure to familiarize yourself with it.
Quick recap on what Cycle.js __INLINE_CODE_177__ function is. Its basically a pure function with the signature:
main :: (Sources) => Sinks
It accepts __INLINE_CODE_178__ as an argument and returns __INLINE_CODE_179__ and describes a dataflow using composition of reactive streams and their transformations with pure functions.
Please note that the __INLINE_CODE_180__ function is only run once per __INLINE_CODE_181__ component lifecycle. It doesn't run again on rerendering or something. This is because the actual call to __INLINE_CODE_182__ is to just build up a dataflow, not to get some rendering data.
The data flows through reactive streams, from __INLINE_CODE_183__ to __INLINE_CODE_184__ of the __INLINE_CODE_185__, until its __INLINE_CODE_186__ are unsubscribed from parent Cycle program's __INLINE_CODE_187__ (this happens when React component is unmounted).
Drivers
The __INLINE_CODE_188__ is supposed to run side effects for a Cycle.js program. Its outside the program itself, supposed to be impure and connects your Cycle app with the "outer world". This is an essential concept and is described in a great detail in Cycle.js docs on drivers.
There are no differences from idiomatic Cycle.js in how __INLINE_CODE_189__ handles __INLINE_CODE_190__ - it just hands it over to __INLINE_CODE_191__ directly. There is only one __INLINE_CODE_192__ driver (an __INLINE_CODE_193__ driver) that is always added internally by the __INLINE_CODE_194__ed component - it allows to invoke functions outside the __INLINE_CODE_195__ that is to be maintained pure.
Wrappers
The __INLINE_CODE_196__ is a rather simple thing - its just a function that takes __INLINE_CODE_197__ function that wraps it with the other __INLINE_CODE_198__ function that might in turn add some logic in between:
function someWrapper(originalMain) {
return wrappedMain(sources) {
// Can do something here, like transforming `sources` somehow
// ...
// Invoke the original `main` internally to obtain its sinks
const sinks = originalMain(sources);
// Can do something to `originalMain`s sinks here
// ...
return {
...sinks,
someOtherSink: xs.of({ ... }) // Can add a new sink
};
}
}
It is important that the wrapping __INLINE_CODE_199__ is maintained pure, so that your entire program is pure. If you need some effectful logic to run in a wrapping __INLINE_CODE_200__, you should consider using driver for that. Wrappers, just like any regular __INLINE_CODE_201__ function, can internally use __INLINE_CODE_202__ that are created from __INLINE_CODE_203__.
Wrappers can also add arbitrary __INLINE_CODE_204__ and could expect certain __INLINE_CODE_205__ to be returned from the Cycle program, depending on particular wrapper implementation.
Wrappers are usually created to encapsulate some non-business-logic stuff, you'd be normally describe anything related to the app itself in the "original" __INLINE_CODE_206__ function.
Most of __INLINE_CODE_207__ (aside from __INLINE_CODE_208__ HOC itself) is implemented with wrappers, that are created for each __INLINE_CODE_209__ed component automatically:
- __INLINE_CODE_210__ (to mess around with React component props)
- __INLINE_CODE_211__ (a gateway to React component lifecycle streams)
- __INLINE_CODE_212__ (to route the view events back from your wrapped React component aka view, and pass the events upstream, to parent components)
Sources
This is a Cycle.js program's input. __INLINE_CODE_213__ function shouldn't care whether something on the __INLINE_CODE_214__ is a __INLINE_CODE_215__ or some custom thing provided by one of the wrappers.
For __INLINE_CODE_216__ connected component, the __INLINE_CODE_217__ are __INLINE_CODE_218__ directly plus whatever __INLINE_CODE_219__ add to them.
Sinks
This is a Cycle.js program's output.
__INLINE_CODE_220__ invokes your __INLINE_CODE_221__ed component's __INLINE_CODE_222__ function, takes its __INLINE_CODE_223__ and subscribes the __INLINE_CODE_224__ passed from the parent component to these __INLINE_CODE_225__. Its how __INLINE_CODE_226__ is "attaching" the new component to a tree of Cycle programs.
If there's no parent component (thus, no __INLINE_CODE_227__) and your component is a "Root Cycle", then the sinks are handled to __INLINE_CODE_228__ to "run" as with a regular Cycle.js app.
React __INLINE_CODE_229__
The __INLINE_CODE_230__ed __INLINE_CODE_231__ component is essentially a view for the associated Cycle.js __INLINE_CODE_232__ function. And __INLINE_CODE_233__ encourages the view to be a "dumb" stateless function that takes __INLINE_CODE_234__ as an argument ad returns something of __INLINE_CODE_235__ type (i.e., __INLINE_CODE_236__s, __INLINE_CODE_237__, etc, whatever __INLINE_CODE_238__ component renders).
Note: The wrapped __INLINE_CODE_239__ shouldn't necessarily be a stateless functional component (SFC), you could have any __INLINE_CODE_240__ component wrapped, but its advisable to use functions for the most part as the whole point of __INLINE_CODE_241__ is to use __INLINE_CODE_242__ for anything that is not a view.
So, the __INLINE_CODE_243__ is the only communication channel from our Cycle.js part (the connected component's __INLINE_CODE_244__ function) to connected __INLINE_CODE_245__ component itself. We therefore need to pass the props from our Cycle program to the view (a __INLINE_CODE_246__ component) somehow.
This is where __INLINE_CODE_247__ comes into play and provides __INLINE_CODE_248__ source and sink for a __INLINE_CODE_249__ function. Its local to a component, so no other components will share the same __INLINE_CODE_250__ source or sink.
__INLINE_CODE_251__ sink
The __INLINE_CODE_252__ sink on __INLINE_CODE_253__ed is there to pass the props to the view:
function main(sources) {
return {
props: xs.of({ greetable: 'World' })
};
}
// Use the passed `greetable` prop
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
Remember, that the value you map to __INLINE_CODE_254__ sink should be an object of new props and that updated props will be merged with existing component __INLINE_CODE_255__ like:
const updatedProps = Object.assign({}, prevProps, newProps);
This means that you can easily overwrite any props you want and leave the others intact.
Also, note above that we're passing the __INLINE_CODE_256__ through an __INLINE_CODE_257__ stream and that might be synchronous (like in our example with a stream of static data that is sent immediately) but could also be asynchronous. In that case the value of some prop might be not yet available on the first render:
function main(sources) {
return {
props: xs.of({ greetable: 'World' }).compose(delay(3000))
};
}
// Trying to use the passed `greetable` prop
// But whooops, no `greetable` yet :(
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
So either provide a default value for that async prop's __INLINE_CODE_258__ or perform additional checks in the view, when rendering.
Note: When messing with the __INLINE_CODE_259__ in your __INLINE_CODE_260__, remember that any props that are passed from outside as an input to a __INLINE_CODE_261__ component, are also there (including callback functions, if there are any) and are also merged upon updates from outside (on __INLINE_CODE_262__).
Lets summarize: the __INLINE_CODE_263__ from the __INLINE_CODE_264__ sink are passed from the __INLINE_CODE_265__ function to the view (__INLINE_CODE_266__ component).
__INLINE_CODE_267__ source
Not only you'll need to provide the props to the view for rendering, but oftentimes you'd need your __INLINE_CODE_268__ to take part in transformations/calculations in the __INLINE_CODE_269__ function itself. For that reason, __INLINE_CODE_270__ provides the __INLINE_CODE_271__ source as well.
It allows you to have an __INLINE_CODE_272__ stream of your component's input __INLINE_CODE_273__. Note that the __INLINE_CODE_274__ is of type __INLINE_CODE_275__ and to take the actual stream of props, use its __INLINE_CODE_276__ attribute:
function main(sources) {
const name$ = sources.props.props$.map(props => props.name);
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
function MyComponent(props) {
return (
<div>
Hello, {props.name}! Your father is Mr. {props.father}.
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
...
// Somewhere else:
<MyConnectedComponent name="Luke Skywalker" father="Darth Vader" />
// renders:
//
// <div>
// Hello, LUKE SKYWALKER! Your father is Mr. Darth Vader.
// </div>
The new value on __INLINE_CODE_277__ is emitted every time __INLINE_CODE_278__ change. This could be overkill for the most cases, as you would only need just few props for your dataflow.
This is what __INLINE_CODE_279__ method for, it accepts the name of the nested prop (will return __INLINE_CODE_280__ if the stream value is not an object) and returns a stream of that prop only that is only got new value once this particular prop changes:
function main(sources) {
const name$ = sources.props.pluck('name');
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
The __INLINE_CODE_281__ed stream also performs shallow comparison and does __INLINE_CODE_282__ behind the scenes, so identical values don't cause a new value emitted on an __INLINE_CODE_283__.
Note that you don't need to always pull all props from your __INLINE_CODE_284__ function's __INLINE_CODE_285__ just for the sake of it to just forward to the __INLINE_CODE_286__ sink directly, you better only pick those you need in a dataflow with __INLINE_CODE_287__. The __INLINE_CODE_288__ passed from outside will be available on the view normally. You can overwrite them from inside your __INLINE_CODE_289__ though.
There's also __INLINE_CODE_290__ method that also accepts a nested prop name. The difference is that the result is not an __INLINE_CODE_291__ stream of prop values but the __INLINE_CODE_292__ instance itself that is scoped to that piece of __INLINE_CODE_293__:
function main(sources) {
const name$ = sources.props.pluck('name'); // Stream of strings
const nameSource = sources.props.select('someNestedObject'); // Scoped ReactPropsSource
const someNestedObjProperty$ = nameSource.pluck('someNestedObjectProperty');
...
}
This is useful when you want to __INLINE_CODE_294__ a stream of some sub-prop from deeper nested objects.
Note: Taking deep property stream this way isn't really nice currently as requires quite a bit of boilerplate - this part of API is already being thought out further and reconsidered.
Interactions
So far so good, but rendering the __INLINE_CODE_295__ isn't the only exciting thing we want to do with __INLINE_CODE_296__, right? We also want to handle events coming from the view(e.g., the DOM).
The way __INLINE_CODE_297__ handles events and nested components' callbacks is the biggest deviation from an idiomatic Cycle.js app using __INLINE_CODE_298__ driver. It refrains from a holistic "no handlers in the view" approach for a few reasons:
- We need something more in line with __INLINE_CODE_299__ as we're using React here
- We're not always handling DOM events but also need to handle some children components data/events passed back. We can't pass streams like in regular Cycle.js app, in __INLINE_CODE_300__ we do it via callbacks that are provided amongst children components' __INLINE_CODE_301__ and we need __INLINE_CODE_302__ed components to be compatible __INLINE_CODE_303__ components at all levels.
This is what "interactions" feature of __INLINE_CODE_304__ is for. It provides the unified interface for parent-child components communication and event handling that is aligned with a regular event/callback handling model on __INLINE_CODE_305__ side but also allows to access those events in form of __INLINE_CODE_306__ streams inside the __INLINE_CODE_307__ function.
Events triggering by the view
Every __INLINE_CODE_308__ component has some additional __INLINE_CODE_309__-specific props injected, so they are available on your view.
These are __INLINE_CODE_310__ which is a function that takes the __INLINE_CODE_311__ (a string) and optional __INLINE_CODE_312__ (of any type) and returns a function that will be a callback for your event or some child component prop function.
So, the signature is:
(interactionType, predefinedValue?) => (value) => void
Its supposed to be called imperatively in your view and is available as __INLINE_CODE_313__:
function MyComponent({ interact }) {
return (
<div>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
For the sake of being cleaner (and for better TypeScript support), __INLINE_CODE_314__ also leverages the ES2015 __INLINE_CODE_315__ feature to provide the object with dynamic properties, each representing an __INLINE_CODE_316__ with __INLINE_CODE_317__ dynamically defined after the function property name. Its available on __INLINE_CODE_318__ so we can rewrite the above snippet as:
function MyComponent({ interactions }) {
return (
<div>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
In the example above, the __INLINE_CODE_319__ function receives the click __INLINE_CODE_320__ as an argument and that is what you'll get in your __INLINE_CODE_321__ (next section).
While cleaner, it doesn't allow for the __INLINE_CODE_322__ to be set, so if you need some particular value to be emitted by the associated stream in your __INLINE_CODE_323__, you can either use __INLINE_CODE_324__ or create an inline function:
// Assuming you have `id` available, e.g.
// when looping/mapping over the collection of items
<button
type="button"
className="delete-btn"
onClick={interact('delete', id)}
>
Click me!
</button>
// OR
<button
type="button"
className="delete-btn"
onClick={() => interactions.delete(id)}
>
Click me!
</button>
View events handling with __INLINE_CODE_325__ source
There's internal __INLINE_CODE_326__ in __INLINE_CODE_327__ and it provides the __INLINE_CODE_328__ source for our __INLINE_CODE_329__ function. The __INLINE_CODE_330__ source is of type __INLINE_CODE_331__, which has the __INLINE_CODE_332__ method, which we can use to obtain interaction streams:
function main(sources) {
const buttonClick$ = sources.interactions.select('buttonClick');
// Do something with a buttonClick$, e.g.
// map it to the click coordinates props
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interact, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
The streams are __INLINE_CODE_333__ed by the same __INLINE_CODE_334__ string you provided to the __INLINE_CODE_335__ function on your view.
Similar to how __INLINE_CODE_336__ object with dynamic function-props is available on your view, __INLINE_CODE_337__ is a ES2015 __INLINE_CODE_338__ and it maintains the dynamic streams right on itself (just don't use __INLINE_CODE_339__ as its reserved method name here here):
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interactions, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
Note: If you try to get the non-existing stream, it will still work, this thing is dynamic, so the stream will be created nevertheless but will just never emit values. Be careful and look out for typos! Using TypeScript is highly recommended to avoid typos here.
Upstream interactions with __INLINE_CODE_340__ sink (calling callbacks on __INLINE_CODE_341__).
Now that we can handle the streams of view events and values from the children components, we would also need to somehow call the callbacks that are passed among our __INLINE_CODE_342__.
First, nothing prevents from following the common React way and call those directly from the view:
function MyComponent({ onButtonClick }) {
return (
<button type="button" onClick={onButtonClick}>
Click me!
</button>
);
}
// Somewhere else:
doSomething = (event) => { console.log('clicked!'); };
...
<MyComponent onButtonClick={doSomething} />
This is fine if you don't need that value's stream in your dataflow. This approach bypasses your Cycle.js program's __INLINE_CODE_343__ completely and doesn't make it impure by otherwise possibly passing an effectful function as __INLINE_CODE_344__. There's even no need to __INLINE_CODE_345__ your component in the above example.
Keep in mind that if __INLINE_CODE_346__ is optional there, you will also need to check whether that is defined before providing that as a callback (use __INLINE_CODE_347__ or TypeScript to mark it as either required or optional).
Oftentimes you will need to call some callback that lives on the __INLINE_CODE_348__ though, and this is what __INLINE_CODE_349__ sink provided by __INLINE_CODE_350__ does:
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
const coords$ = buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
);
// We both update the `props` (to render it in this component)
// and pass upstream
return {
props: coords$,
interactions: xs.of({
onCoordsChange: coords$
})
};
}
function MyComponent({ interactions }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
handleCoordsChange = (coords) => {
console.log('New coordinates received: ', coords);
};
...
<ConnectedComponent onCoordsChange={handleCoordsChange} />
// The new coords will be rendered by the component
// and the console will show the "New coordinates received"
// with new coords too.
Once the appropriate stream emits a value, that callback is called. It will also check if the callback was passed in the first place to skip the call attempt if no callback with a given name is passed among the __INLINE_CODE_351__.
Important: Note that __INLINE_CODE_352__ sink expects a stream of object whose property names correspond to callback names on __INLINE_CODE_353__ and property values are themselves streams too - whatever values those streams emit, will be passed as an argument to the callback.
So far so good, but we know that our __INLINE_CODE_354__ is a wrapper and not a driver, so what if one passed the effectful function as a callback, e.g., __INLINE_CODE_355__ passed as __INLINE_CODE_356__ above does a __INLINE_CODE_357__ internally - that is a side effect, right?
Well, this is why the function is not invoked directly, or inside the wrapper (since the wrapper is the part of the pure land of our __INLINE_CODE_358__s), its using the __INLINE_CODE_359__ driver internally. More on this below in a section about Side-effect control, just keep in mind that __INLINE_CODE_360__ and __INLINE_CODE_361__ should be pure and __INLINE_CODE_362__ follows that requirement.
React component lifecycle streams
You're probably not going to need __INLINE_CODE_363__ component lifecycle hooks often inside your __INLINE_CODE_364__, but still - they are provided via special __INLINE_CODE_365__. It provides the __INLINE_CODE_366__ source that is available on each __INLINE_CODE_367__ed __INLINE_CODE_368__ sources.
The lifecycle events are provided as __INLINE_CODE_369__ streams and their names mostly mirror the React lifecycle methods, but shortened to remove some redundant __INLINE_CODE_370__ prefixes:
React lifecycle method
__INLINE_CODE_371__ source property
Stream value
__INLINE_CODE_372__
__INLINE_CODE_373__
__INLINE_CODE_374__
__INLINE_CODE_375__
__INLINE_CODE_376__
__INLINE_CODE_377__
__INLINE_CODE_378__
__INLINE_CODE_379__
__INLINE_CODE_380__
__INLINE_CODE_381__
__INLINE_CODE_382__
__INLINE_CODE_383__
__INLINE_CODE_384__
__INLINE_CODE_385__
__INLINE_CODE_386__
__INLINE_CODE_387__
__INLINE_CODE_388__
__INLINE_CODE_389__
__INLINE_CODE_390__
__INLINE_CODE_391__
Error data
The "error data" on the __INLINE_CODE_392__ is an object of this shape:
{
error: Error;
errorInfo: ErrorInfo;
}
These are basically React __INLINE_CODE_393__ arguments squashed into a single object.
Also, note that __INLINE_CODE_394__ hook is not present there, since that is not really an event and affects how your component is rendered. __INLINE_CODE_395__ would be providing its own custom internal __INLINE_CODE_396__ instead for optimisation purposes and you could implement it on your wrapped component anyway.
Note: As well, no lifecycle events are passing anything related to React component __INLINE_CODE_397__, only taking __INLINE_CODE_398__ into account instead. This is because the __INLINE_CODE_399__ is meant to be managed differently when Cycle.js is in use. You can still use that for wrapped component but its out of play on the component generated by __INLINE_CODE_400__.
A quick example:
function main(sources) {
const willUnmount$ = { sources.lifecycle };
// Do something on unmounting, e.g. report that to a console
// via some hypothetical `logger` driver
return {
logger: willUnmount$.mapTo(`I'll be unmounted soon!`)
};
}
Side-effect control and __INLINE_CODE_401__ driver
Side-effect control is where Cycle.js really shines (not only implementation, it applies to the "cycle pattern" behind Cycle.js in the first place).
In order to keep the __INLINE_CODE_402__ part of __INLINE_CODE_403__ed component pure, we need to make sure that any potentially effectful function is not run inside our __INLINE_CODE_404__ directly.
This is what __INLINE_CODE_405__ driver is responsible for. It allows to only describe what function you want to call and with what arguments, then delegate that to a __INLINE_CODE_406__ which is supposed to run any impure functions outside of the app itself.
The way it works is very similar to that of __INLINE_CODE_407__ driver from __INLINE_CODE_408__ package where you don't actually run the request inside your __INLINE_CODE_409__ but just describe it and pass to __INLINE_CODE_410__ driver to handle. __INLINE_CODE_411__ driver is just a bit more generic and operates on functions.
__INLINE_CODE_412__ uses __INLINE_CODE_413__ driver internally (to run effects for upstream __INLINE_CODE_414__), but it is also available for you on any connected __INLINE_CODE_415__ as __INLINE_CODE_416__ sink automatically (i.e., without explicit configuration on a root cycle):
function log() {
console.log(`I'm effectful!`);
}
function saveToLocalStorage(message) {
localStorage.setItem('message', message);
}
function main(sources) {
const someCallbackFn$ = sources.props.pluck('someCallbackFunction');
return {
fnCallEffect: xs.merge(
xs.of({ fn: log }),
xs.of({ fn: saveToLocalStorage, args: ['some message to save'] }),
// Call the `props` callback function directly, this is how
// `interactions` sink works behind the scenes:
someCallbackFn$.map(fn => ({ fn }))
)
};
}
Isolation
Isolation is Cycle.js way of making the component's __INLINE_CODE_417__ and __INLINE_CODE_418__ isolated from influence of other components.
Make sure to carefully read more about Cycle.js isolation here. It might take a bit to get used to an idea.
To reiterate: the concept of isolation is important for Cycle.js apps. Its a little bit different for __INLINE_CODE_419__ because most things are scoped to a particular __INLINE_CODE_420__ed component already, like components have "local" __INLINE_CODE_421__, __INLINE_CODE_422__ and __INLINE_CODE_423__ sources and sinks and __INLINE_CODE_424__ components are also self-contained already.
There are though __INLINE_CODE_425__/__INLINE_CODE_426__ that will need the isolation feature, like __INLINE_CODE_427__ driver or __INLINE_CODE_428__ wrapper (a state management solution for Cycle.js apps). So, isolation makes sense for shared __INLINE_CODE_429__/__INLINE_CODE_430__, those you pass to __INLINE_CODE_431__ function (by defining the __INLINE_CODE_432__/__INLINE_CODE_433__ options on root __INLINE_CODE_434__) and that will be available across the entire tree of Cycle programs.
Note: The isolation works by scoping the source streams to particular component and a kind of unscoping of the sink streams before connecting to parent's sinks.
Also note, that each driver/wrapper decides on its own how its sources/sinks should be isolated.
In Cycle.js, you use the __INLINE_CODE_435__ function from __INLINE_CODE_436__ package directly, as you're just invoking the children __INLINE_CODE_437__ functions from your current component's __INLINE_CODE_438__ function:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// Do something with childSinks, combine somehow, etc
return {
...
};
}
__INLINE_CODE_439__ function has this simple signature:
isolate :: (MainFn, scope) => MainFn
It takes the __INLINE_CODE_440__ function and an optional __INLINE_CODE_441__ and returns an isolated __INLINE_CODE_442__ function.
__INLINE_CODE_443__ does also use the __INLINE_CODE_444__ package internally, but you can't call __INLINE_CODE_445__ directly on some __INLINE_CODE_446__ function. You'd never call one component's __INLINE_CODE_447__ from the other component's __INLINE_CODE_448__ directly with __INLINE_CODE_449__.
Naive explicit isolation (conceptually incorrect)
Just to illustrate the issue, there's an attempt to isolate the component's __INLINE_CODE_450__ sources, so that HTTP requests initiated from that components are scoped to it (and are unavailable to siblings).
Since we know that __INLINE_CODE_451__ returns a __INLINE_CODE_452__ function and __INLINE_CODE_453__ takes the __INLINE_CODE_454__ function as an argument, lets try to feed it an isolated version of it:
function main(sources) {
...
}
function MyComponent() {
...
}
const isolatedMain = isolate(main, { HTTP: 'myComponent' });
const ConnectedComponent = cycleConnect(isolatedMain)(MyComponent);
While this will won't raise any errors (because this is valid code after all), this approach brings a subtle issue.
This will isolate things for this particular components but not their children components, so in this case, children (connected childred __INLINE_CODE_455__ components) will have no __INLINE_CODE_456__ isolation scope for __INLINE_CODE_457__ channel.
This might seems like a no-issue, because hey, you can define different scopes for the children components right? Well, while this is technically possible, its conceptually incorrect and it will be completely broken in some cases, e.g., with __INLINE_CODE_458__ where the deeper you go - the "more filtered" source streams you get.
Important! The above note about it being "conceptually incorrect" refers to the way __INLINE_CODE_459__ maintains the tree of Cycle.js programs internally. It tries to make the entire app structure look the same way as you'd manually write a Cycle.js app. So, when you __INLINE_CODE_460__ at some level, then if inside your __INLINE_CODE_461__ you also call some __INLINE_CODE_462__ happens, then __INLINE_CODE_463__ gets isolated sources no matter what.
__INLINE_CODE_464__ option for __INLINE_CODE_465__
The first way to correctly isolate __INLINE_CODE_466__ed component is to use the __INLINE_CODE_467__ option for __INLINE_CODE_468__ itself, so the above can be rewritten as:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main, {
isolate: { HTTP: 'myComponent' }
})(MyComponent);
This will work perfectly, with all children __INLINE_CODE_469__ed components having __INLINE_CODE_470__ namespace applied to __INLINE_CODE_471__ sources/sinks in their __INLINE_CODE_472__ functions.
The __INLINE_CODE_473__ option accepts a __INLINE_CODE_474__ or the __INLINE_CODE_475__ and in that case its passed directly to __INLINE_CODE_476__ package's __INLINE_CODE_477__ as a __INLINE_CODE_478__ argument. So, check out the __INLINE_CODE_479__ function API reference.
__INLINE_CODE_480__ also makes it accept a function that takes component __INLINE_CODE_481__ as an argument and should return the scope of __INLINE_CODE_482__ or __INLINE_CODE_483__ type that is then also passed to __INLINE_CODE_484__ package's __INLINE_CODE_485__. This allows for some dynamic isolation scopes to be set based on the React component's initial props.
Important: Despite the __INLINE_CODE_486__ is provided as an option to __INLINE_CODE_487__ when defining the connected component, the actual isolation will only happen when component instance is created. This is why its possible to dynamically resolve the __INLINE_CODE_488__ based on the component __INLINE_CODE_489__ in __INLINE_CODE_490__.
__INLINE_CODE_491__ prop
Despite the __INLINE_CODE_492__ option will work nicely for some cases, there's a pattern in how it is utilized in pure Cycle.js programs. Lets take a look at pure Cycle.js example again:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// ...
return {
...
};
}
The important part is that in Cycle.js app its not a child who decides how to isolate itself but __INLINE_CODE_493__ is provided from the parent instead.
Most of the time, we want to maintain the same approach with __INLINE_CODE_494__. The advantage is that component in this case is totally agnostic from the provided scope, it doesn't even care whether its isolated or not at all - all it knows is that it expects certain __INLINE_CODE_495__ and returns certain __INLINE_CODE_496__. This makes the component reusable.
This is especially important with __INLINE_CODE_497__, where you want to control where your child components "attach" to the state.
In other words, the parent decides how to compose children components, not the other way around, because otherwise the child would need to have an intimate knowledge of something that really belongs to parent and thus not being reusable and breaking encapsulation.
For this reason, every __INLINE_CODE_498__ed React component accepts an __INLINE_CODE_499__ prop, so you can use that just like any other prop:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
// Isolate everything
<ConnectedComponent isolate="myComponent" />
// Isolate HTTP only
<ConnectedComponent isolate={{ HTTP: 'myComponent' }} />
<ConnectedComponent isolate={{ HTTP: 'myComponent_2' }} />
In contrast with __INLINE_CODE_500__ option, the __INLINE_CODE_501__ prop can only accept __INLINE_CODE_502__ or __INLINE_CODE_503__ that is directly passed to __INLINE_CODE_504__ function from __INLINE_CODE_505__, thus maintaining full parity with pure Cycle.js approach. Indeed, having a function that accepts initial __INLINE_CODE_506__ wouldn't make much sense because you're anyway providing props that could be different for different component instances when rendering that component.
__INLINE_CODE_507__ helper component
Since isolation in __INLINE_CODE_508__ applies to both "current" __INLINE_CODE_509__ed component and all its __INLINE_CODE_510__ed children, we might sometimes need to define some connected component to only be a common parent for some children, which must provide isolation but wouldn't contain any logic.
This is what a helper __INLINE_CODE_511__ component for. It accepts the __INLINE_CODE_512__ prop (that is directly passed as a __INLINE_CODE_513__ argument to __INLINE_CODE_514__ function of __INLINE_CODE_515__):
<Isolator scope={{ HTTP: 'someNamespace' }}>
<ConnectedComponent someProps="1" />
<ConnectedComponent someProps="2" />
<ConnectedComponent someProps="3" />
</Isolator>
This assists the isolation composition at view level.
Extra: State management with __INLINE_CODE_516__
__INLINE_CODE_517__ is the idiomatic Cycle.js fractal state management solution. It is tailored for Cycle.js apps but since __INLINE_CODE_518__ is using the real Cycle.js, it stays fully compatible here as well.
Using __INLINE_CODE_519__ with __INLINE_CODE_520__ is optional, since you might be using different lib (e.g., __INLINE_CODE_521__) for that.
To start, just read the onionify docs and install the package:
$ npm install --save cycle-onionify
and pass that with __INLINE_CODE_522__ array to a __INLINE_CODE_523__ connected component (e.g., Root Cycle):
// RootCycle.js
import { run } from '@cycle/run';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
wrappers
})();
export default RootCycle;
Now every __INLINE_CODE_524__ed component that is a child of the above-mentioned __INLINE_CODE_525__ in React component tree got the __INLINE_CODE_526__ source and sink.
Important: Notice that you're all set - its purely a Cycle.js land. __INLINE_CODE_527__ doesn't provide any additional functionality for __INLINE_CODE_528__. Instead, it provides few helpers for isolation and working with collections on the React side.
All the exportables related to __INLINE_CODE_529__ reside in the extra package: __INLINE_CODE_530__. It is installed along with the main lib but is not __INLINE_CODE_531__d/__INLINE_CODE_532__ed by default, so won't be bundled (by Webpack, Rollup, etc) unless you're using it.
This extra sub-package contains few things:
- __INLINE_CODE_533__ helper component
- __INLINE_CODE_534__ helper component
- __INLINE_CODE_535__ helper factory function
Read ahead for details on each.
In __INLINE_CODE_536__, the state/reducers layer composition is done via isolation, so the first thing here is __INLINE_CODE_537__:
__INLINE_CODE_538__ helper component
This is pretty much like __INLINE_CODE_539__ but is here to specifically isolate __INLINE_CODE_540__ sources/sinks only.
So, the:
import { StateIsolator } from 'react-cycle-connect/lib/extra/onionify';
// ...
<StateIsolator lens="someStateProperty">
<NestedComponent />
</StateIsolator>
is somewhat a convenient shortcut to:
import { Isolator } from 'react-cycle-connect';
// ...
<Isolator scope={{ onion: 'someStateProperty', '*': null }}>
<NestedComponent />
</Isolator>
Notice how __INLINE_CODE_541__'s prop is explicitly named __INLINE_CODE_542__. It just implies that oftentimes you'll be using "lens" to isolate the state. Consider a string just a shortcut to a more elaborate __INLINE_CODE_543__ object.
In the example above, the __INLINE_CODE_544__'s __INLINE_CODE_545__ function __INLINE_CODE_546__ will represent only that "someStateProperty" piece of state. Pretty much how you'd __INLINE_CODE_547__ the __INLINE_CODE_548__ channel in an idiomatic Cycle.js app.
There's also a __INLINE_CODE_549__ property, so that you can change the name of the source/sink inside your __INLINE_CODE_550__ to something else. Thats to be on par with __INLINE_CODE_551__ features, where you can also change that name.
Handling collection state manually
Managing collections of components in pure Cycle.js with __INLINE_CODE_552__ is a kind of a recognized challenge. This is why __INLINE_CODE_553__ ended up providing __INLINE_CODE_554__ helper function - see their docs for more detail.
While __INLINE_CODE_555__ also has a __INLINE_CODE_556__ helper component that is very similar to __INLINE_CODE_557__ __INLINE_CODE_558__ function, lets first try to pretend we don't have it and imagine how'd we handle the state without it.
Note: Nothing prevents you from skipping right to a description of how __INLINE_CODE_559__ works, but it might be useful to understand the rationale behind it, so reading this section is recommended.
Handling collections is different with React than in Cycle.js, e.g., you can always just render React components in a loop or __INLINE_CODE_560__ an array of data to an array of components and React will render that.
When you want to render some __INLINE_CODE_561__ data from your state, its easy without any helpers:
/*
Assume you have this value on your `List` component `stateFork
- updated to more recent version of cycle, including @cycle/state
- updated to React 16.9 api, including new lifecycles and context api
- merged onionify separate lib so import is __INLINE_CODE_0__
- added tests
- published at @iilab/react-cycle-connect
react-cycle-connect
An experimental library that allows using Cycle.js as a reactive dataflow framework for React.
Written in TypeScript.
This little thing allows to define your app's view layer using regular React components, but implement the actual logic behind it using pure functional reactive approach, taking advantage of Cycle.js' Model-View-Intent pattern and its exceptional side effect control.
This isn't a custom implementation of Cycle pattern for __INLINE_CODE_1__ but a link to real Cycle.js instead, so any Cycle.js drivers and wrappers can be applied (e.g., __INLINE_CODE_2__ for state management). Also, for this reason, some knowledge about how __INLINE_CODE_3__ works is assumed.
Note: This is an experimental lib and currently works with __INLINE_CODE_4__ and __INLINE_CODE_5__ only. If all goes good, the support for __INLINE_CODE_6__/__INLINE_CODE_7__ (instead of __INLINE_CODE_8__) and for __INLINE_CODE_9__/__INLINE_CODE_10__ (instead of __INLINE_CODE_11__) is anticipated. Nothing prevents from having __INLINE_CODE_12__ covered too.
UPDATE (MAR 2018): Some internals overhaul is pending and it's currently waiting for Cycle.js to settle on hybrid push-pull reactivity and have internals rewritten to use Callbags instead of __INLINE_CODE_13__. So, __INLINE_CODE_14__ is going to be using Callbags by default too.
Any feedback is highly appreciated!
Table of Contents
- react-cycle-connect
- Table of Contents
- Shup up and show me the code
- Installation
- Motivation
- How it works (architecture)
- Guide
- API reference
- Alternatives / Competitors
- FAQ
- Roadmap
Shup up and show me the code
Cycle.js's counter example ported over:
import React from 'react';
import ReactDOM from 'react-dom';
import xs from 'xstream';
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
// Your Cycle.js `main` function
function main(sources) {
const action$ = xs.merge(
sources.interactions.decrement.map(ev => -1),
sources.interactions.increment.map(ev => +1)
);
const count$ = action$.fold((acc, x) => acc + x, 0);
return {
props: count$.map(count => ({ count }))
};
}
// Your React component
function App({ interactions, count }) {
return (
<div>
<button onClick={interactions.decrement}>Decrement</button>
<button onClick={interactions.increment}>Increment</button>
<p>Counter: {count}</p>
</div>
);
}
// Some connection magic
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
// Render normally as it's a regular React app
ReactDOM.render(
<ConnectedApp />,
document.getElementById('root')
);
Also - there's official TodoMVC example with __INLINE_CODE_33__ for state management.
Installation
Using npm:
$ npm install --save react-cycle-connect
Using yarn:
$ yarn add react-cycle-connect
Motivation
The motivation to create this lib was based off the need to retain the React-style composability of the view layer using regular React components, reuse existing React components, make it (the lib) easily/incrementally adoptable in an ongoing React project, but also take advantage of the Cycle.js' MVI pattern and side effect control.
At the same time, while the most valuable thing from __INLINE_CODE_34__ is the pattern itself, there are numerous available libs/plugins for Cycle.js to take advantage of, letting us take the best of both worlds.
This way, you can still use anything from React stack (e.g., __INLINE_CODE_35__ to compose your routed app screens), but describe the interactions as a dataflow, with pure functions and reactive streams. Most of React components are going to be "dumb" (and also stateless functional aka SFC) since all the backing logic and cross-component communication would be handled by a tree of __INLINE_CODE_36__ programs.
Why not just use Cycle.js alone?
That is what I'd recommend considering in the first place. Its fast, its cool, the __INLINE_CODE_37__ (virtual DOM rendering engine behind Cycle's DOM driver) is goddamn fast and reliable. If you don't intend to use __INLINE_CODE_38__ components in any way, don't even get bothered. The __INLINE_CODE_39__ is more about "introducing Cycle.js to __INLINE_CODE_40__", not the other way around.
Is it a "Cycle.js in React" or "React in Cycle.js"?
More of a "Cycle.js in React". The app is bootstrapped by rendering a React component to the DOM, view is composed the React way. The root Cycle.js program is being __INLINE_CODE_41__ by rendering a root __INLINE_CODE_42__ed component. Though, it doesn't prescribe how you write your Cycle.js programs, and aside from the DOM generation part, its just Cycle.js all the way.
How it works (architecture)
Every __INLINE_CODE_43__ed component got a real Cycle.js program "attached" to it. The boundary between the two is __INLINE_CODE_44__. From your Cycle.js program standpoint, __INLINE_CODE_45__ is a view. Cycle.js program can update __INLINE_CODE_46__ that React component will then render.
A bunch of nested React components in your app represent a "tree of React components". __INLINE_CODE_47__ maintains a "tree of Cycle.js programs" that map to __INLINE_CODE_48__ed components and takes advantage of React's context feature to provide links between the nodes. Each connected component provides scoped links to the children nodes .
New "Cycle nodes" are attached lazily, upon associated React component initialization (this is where React controls how Cycle programs are run). The above-mentioned "link" is just an object on React __INLINE_CODE_49__ that brings couple properties:
- __INLINE_CODE_50__ for the child connected component's __INLINE_CODE_51__ function; and
- __INLINE_CODE_52__ to subscribe to that component's __INLINE_CODE_53__ function __INLINE_CODE_54__.
This is where something unusual for the Cycle.js happens - we subscribe to and unsubscribe from connected component's __INLINE_CODE_55__ function __INLINE_CODE_56__ based on React component's lifetime hooks. In Cycle.js, you normally just run the app at startup and let it run as an ongoing process. But this is where __INLINE_CODE_57__ performs the sync between the two worlds.
Overall, aside from using React __INLINE_CODE_58__ (which is just an implementation detail) and some intense wrapping on the __INLINE_CODE_59__ side to make laziness possible, __INLINE_CODE_60__ tries to represent the entire tree of Cycle.js programs as close as possible to what you'd end up with by writing idiomatic Cycle.js with explicit calling of nested component's __INLINE_CODE_61__ functions from the parent component __INLINE_CODE_62__ function. The result of children components is always __INLINE_CODE_63__d.
Aside from passing the __INLINE_CODE_64__ to children components, __INLINE_CODE_65__ also explicitly applies some internal wrappers to integrate with React component: __INLINE_CODE_66__, __INLINE_CODE_67__ and __INLINE_CODE_68__, that are scoped to a connected component's Cycle.js program only. It also explicitly runs the internal __INLINE_CODE_69__ driver for the __INLINE_CODE_70__ nodes by default - this is necessary for the __INLINE_CODE_71__ sink to work (because its supposed to run callbacks passed via __INLINE_CODE_72__ and that might be an effectful function).
Note that the entire internal "tree" of Cycle.js programs is maintained pure, including internal component-scoped wrappers, just as you would do with regular Cycle.js program.
Differences from idiomatic Cycle.js usage
Note: there's a difference in how'd you write the __INLINE_CODE_73__/__INLINE_CODE_74__ app vs pure __INLINE_CODE_75__ app. There's no misuse of Cycle.js per se, we just don't use it for rendering with __INLINE_CODE_76__ - and that applies certain implications to the approach.
__INLINE_CODE_77__ and __INLINE_CODE_78__, while both allow to create user interfaces, imply different approaches to the app architecture. The most important part here is that they are conceptually different when it comes to generating the VDOM.
In order to combine the two approaches nicely, there are certain deviations from how'd you write the __INLINE_CODE_79__-only app to allow for more optimal React DX.
The main difference from a pure __INLINE_CODE_80__ way is that its not a Cycle program that technically generates VDOM - instead, there's React component to represent a piece of UI. __INLINE_CODE_81__ approach to view/__INLINE_CODE_82__ event handling is rather purist - view only defines the markup and nothing else (i.e., user interaction/event handlers are defined inside Cycle program itself and not as some view component __INLINE_CODE_83__/etc attribute). That is not a crazy random opinion but instead a very well thought and weighted out decision by Cycle.js team. __INLINE_CODE_84__ takes a different approach, which isn't that strict and is more in line with React, with callbacks amongst __INLINE_CODE_85__ and all that stuff. It enables the events/component callbacks handling via interactions.
The view composition is obviously completely different. In __INLINE_CODE_86__ you run each component's __INLINE_CODE_87__ function that (possibly) generates a VDOM node and then explicitly combine the pieces of markup into a single VDOM stream that is then given to a DOM driver. With __INLINE_CODE_88__ you compose the view layer with React components normally. You can just connect those to Cycle.js program to "outsource" the actual logic to it.
Guide
This assumes the preliminary knowledge of both __INLINE_CODE_89__ and __INLINE_CODE_90__ (and __INLINE_CODE_91__). It doesn't provide much of the insight on how these work, but is instead focused on how __INLINE_CODE_92__ combines the two.
Basics
The result of __INLINE_CODE_93__ call is a HOC that optionally takes a React component (similar to that __INLINE_CODE_94__ function of __INLINE_CODE_95__) and returns a component that is augmented with backing Cycle.js program. This Cycle.js program is internally wrapped into a "Cycle Node" that, when initialized, is connected to a tree of Cycle.js programs. "Cycle Nodes" of every __INLINE_CODE_96__ed components are linked together via React __INLINE_CODE_97__.
Note, though, that there are no direct references from one component's Cycle program to another component's Cycle program, i.e., you're not invoking one __INLINE_CODE_98__ function from the other (this is perfectly possible though, but since we're not using __INLINE_CODE_99__ here, it would only handle non-DOM stuff). Instead, __INLINE_CODE_100__ handles the linking behind the scenes, so the "sibling" Cycle programs are fed with the same __INLINE_CODE_101__ and subscribe their __INLINE_CODE_102__ to the same __INLINE_CODE_103__.
There's essential concept of isolation in Cycle.js, so every __INLINE_CODE_104__ed component accepts an __INLINE_CODE_105__ prop, that is directly passed to __INLINE_CODE_106__ function from __INLINE_CODE_107__ package. Read more about Cycle.js isolation here.
Root Cycle
Every __INLINE_CODE_108__ed component provides a __INLINE_CODE_109__ object to its children through React __INLINE_CODE_110__ and also expects that __INLINE_CODE_111__ from some parent component (not necessarily immediate parent).
That link object has the following shape:
{
// Sources to provide to our Cycle.js program's `main` function as an argument
sources: { ... },
// sinkProxies to subscribe to our Cycle program's `main` function sinks
sinkProxies: { ... }
}
You shouldn't be working with that directly but its important that something needs to provide the top-level __INLINE_CODE_112__ and __INLINE_CODE_113__ in the first place. This is what __INLINE_CODE_114__ option of cycle connect for.
Also, the __INLINE_CODE_115__ed components' programs (__INLINE_CODE_116__ functions) are lazily attached to already running upper-level Cycle.js program, so the __INLINE_CODE_117__ program has to be __INLINE_CODE_118__.
So, root __INLINE_CODE_119__, aside from __INLINE_CODE_120__ option, also needs __INLINE_CODE_121__ defined, so that it knows how to run your top-level Cycle.js program. This would normally be just a __INLINE_CODE_122__ function from __INLINE_CODE_123__ package (or __INLINE_CODE_124__, or __INLINE_CODE_125__, depending on the reactive streams library in use).
You could just __INLINE_CODE_126__ your top-level __INLINE_CODE_127__ component like we did in the first example:
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
...
function main(sources) {
...
}
function App() {
return (
// Some JSX
...
);
}
const ConnectedApp = cycleConnect(main, {
root: true,
runFn: run
})(App);
Though this will work, its advisable to create a specialized __INLINE_CODE_128__ component just for this purpose:
// RootCycle.js
import { run } from '@cycle/run';
import { cycleConnect } from 'react-cycle-connect';
function main(sources) {
...
}
export const RootCycle = cycleConnect(main, {
root: true,
runFn: run
})(); // No component to wrap? Its okay, read ahead
export default RootCycle;
The top-level Cycle sources are actually __INLINE_CODE_129__ and __INLINE_CODE_130__, so root Cycle is also in charge of providing these __INLINE_CODE_131__ and __INLINE_CODE_132__ to any Cycle.js program down the tree, as __INLINE_CODE_133__.
__INLINE_CODE_134__ option is the object with keys defining properties of the __INLINE_CODE_135__ object, identical to how you'd do that in a regular Cycle.js program.
__INLINE_CODE_136__ option accepts an array of wrappers (order might matter there, so its a user-land option).
Also, the __INLINE_CODE_137__ function is optional (because you might need a __INLINE_CODE_138__ to be a __INLINE_CODE_139__ or for __INLINE_CODE_140__ only), so you can skip it, unless you have some logic to implement there.
Note that the __INLINE_CODE_141__ can be passed to any __INLINE_CODE_142__, even non-root, but __INLINE_CODE_143__ option is only valid for __INLINE_CODE_144__ Cycle and is ignored otherwise.
// RootCycle.js
import { run } from '@cycle/run';
import { makeHTTPDriver } from '@cycle/http';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const drivers = { HTTP: makeHTTPDriver() };
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
export default RootCycle;
__INLINE_CODE_145__ component is basically in charge of bootstrapping the "Cycle" part of your app here.
Then just wrap your __INLINE_CODE_146__ with it and e.g., render with __INLINE_CODE_147__:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import RootCycle from './RootCycle';
ReactDOM.render(
(
<RootCycle>
<App />
</RootCycle>
),
document.getElementById('root')
);
__INLINE_CODE_148__ option
You might have noticed in axamples above that we're not providing any component to a __INLINE_CODE_149__ HOC, leaving empty braces instead:
const ConnectedComponent = cycleConnect({
root: true,
runFn: run,
drivers,
wrappers
})();
Well, that is not a mistake - the wrapped component is optional. When a "component to wrap" is provided to __INLINE_CODE_150__, then if we render the connected component, the __INLINE_CODE_151__ will also render __INLINE_CODE_152__ internally.
Thats all good and works (and what you'll be using most of the time anyway), but sometimes you just want to keep your React Element tree a bit cleaner when the wrapped component would be a kind of noop anyway (just forwarding __INLINE_CODE_153__ and nothing else), or component is some technical/lower-level like __INLINE_CODE_154__, or have some special rendering behavior.
So, we want to avoid seeing this in React Developer Tools:
<cycleConnect(SomeComponent)>
<SomeComponent>
...
</SomeComponent>
</cycleConnect(SomeComponent)>
You can use __INLINE_CODE_155__ option for that. It accepts the function with this signature:
(props) => ReactNode | null
If no component is provided for wrapping, nor __INLINE_CODE_156__ option is defined, the __INLINE_CODE_157__ option defaults to __INLINE_CODE_158__ function, so its either forwarding __INLINE_CODE_159__ if any or renders nothing.
If both __INLINE_CODE_160__ option is provided and component-to-wrap is passed, then __INLINE_CODE_161__ takes the precedence.
Note: Why not use the __INLINE_CODE_162__ option in place of wrapped component all the time? Well, its technically possible, espeically if your React component is a stateless functional component anyway.
But regular HOC wrapping is just common technique in React world and is nicely composable when you have other HOCs than __INLINE_CODE_163__.
And is cleaner too - you might be prototyping your app as a set of __INLINE_CODE_164__ components first, without even involving __INLINE_CODE_165__, then "connect" them all at once, or introduce __INLINE_CODE_166__ incrementally. Whatever :)
__INLINE_CODE_167__ option
Your component's __INLINE_CODE_168__ is by default automatically assigned to the following (depending on whether the wrapped component is provided, or __INLINE_CODE_169__ option, or neither:
- __INLINE_CODE_170__ when WrappedComponent is passed
- __INLINE_CODE_171__ when __INLINE_CODE_172__ prop is defined (and is truthy)
- __INLINE_CODE_173__ otherwise
You can override that like with any regular React component:
const ConnectedComponent = cycleConnect({
...
)(Component);
ConnectedComponent.displayName = 'ConnectedComponent';
But there's also a __INLINE_CODE_174__ option to set the generated (connected) component __INLINE_CODE_175__, which looks a bit cleaner:
const ConnectedComponent = cycleConnect({
...
displayName: 'ConnectedComponent'
)(Component);
Inside the Cycle
__INLINE_CODE_176__ really assumes that you know how to use Cycle.js. Its really a very simple yet very elegant pattern, so make sure to familiarize yourself with it.
Quick recap on what Cycle.js __INLINE_CODE_177__ function is. Its basically a pure function with the signature:
main :: (Sources) => Sinks
It accepts __INLINE_CODE_178__ as an argument and returns __INLINE_CODE_179__ and describes a dataflow using composition of reactive streams and their transformations with pure functions.
Please note that the __INLINE_CODE_180__ function is only run once per __INLINE_CODE_181__ component lifecycle. It doesn't run again on rerendering or something. This is because the actual call to __INLINE_CODE_182__ is to just build up a dataflow, not to get some rendering data.
The data flows through reactive streams, from __INLINE_CODE_183__ to __INLINE_CODE_184__ of the __INLINE_CODE_185__, until its __INLINE_CODE_186__ are unsubscribed from parent Cycle program's __INLINE_CODE_187__ (this happens when React component is unmounted).
Drivers
The __INLINE_CODE_188__ is supposed to run side effects for a Cycle.js program. Its outside the program itself, supposed to be impure and connects your Cycle app with the "outer world". This is an essential concept and is described in a great detail in Cycle.js docs on drivers.
There are no differences from idiomatic Cycle.js in how __INLINE_CODE_189__ handles __INLINE_CODE_190__ - it just hands it over to __INLINE_CODE_191__ directly. There is only one __INLINE_CODE_192__ driver (an __INLINE_CODE_193__ driver) that is always added internally by the __INLINE_CODE_194__ed component - it allows to invoke functions outside the __INLINE_CODE_195__ that is to be maintained pure.
Wrappers
The __INLINE_CODE_196__ is a rather simple thing - its just a function that takes __INLINE_CODE_197__ function that wraps it with the other __INLINE_CODE_198__ function that might in turn add some logic in between:
function someWrapper(originalMain) {
return wrappedMain(sources) {
// Can do something here, like transforming `sources` somehow
// ...
// Invoke the original `main` internally to obtain its sinks
const sinks = originalMain(sources);
// Can do something to `originalMain`s sinks here
// ...
return {
...sinks,
someOtherSink: xs.of({ ... }) // Can add a new sink
};
}
}
It is important that the wrapping __INLINE_CODE_199__ is maintained pure, so that your entire program is pure. If you need some effectful logic to run in a wrapping __INLINE_CODE_200__, you should consider using driver for that. Wrappers, just like any regular __INLINE_CODE_201__ function, can internally use __INLINE_CODE_202__ that are created from __INLINE_CODE_203__.
Wrappers can also add arbitrary __INLINE_CODE_204__ and could expect certain __INLINE_CODE_205__ to be returned from the Cycle program, depending on particular wrapper implementation.
Wrappers are usually created to encapsulate some non-business-logic stuff, you'd be normally describe anything related to the app itself in the "original" __INLINE_CODE_206__ function.
Most of __INLINE_CODE_207__ (aside from __INLINE_CODE_208__ HOC itself) is implemented with wrappers, that are created for each __INLINE_CODE_209__ed component automatically:
- __INLINE_CODE_210__ (to mess around with React component props)
- __INLINE_CODE_211__ (a gateway to React component lifecycle streams)
- __INLINE_CODE_212__ (to route the view events back from your wrapped React component aka view, and pass the events upstream, to parent components)
Sources
This is a Cycle.js program's input. __INLINE_CODE_213__ function shouldn't care whether something on the __INLINE_CODE_214__ is a __INLINE_CODE_215__ or some custom thing provided by one of the wrappers.
For __INLINE_CODE_216__ connected component, the __INLINE_CODE_217__ are __INLINE_CODE_218__ directly plus whatever __INLINE_CODE_219__ add to them.
Sinks
This is a Cycle.js program's output.
__INLINE_CODE_220__ invokes your __INLINE_CODE_221__ed component's __INLINE_CODE_222__ function, takes its __INLINE_CODE_223__ and subscribes the __INLINE_CODE_224__ passed from the parent component to these __INLINE_CODE_225__. Its how __INLINE_CODE_226__ is "attaching" the new component to a tree of Cycle programs.
If there's no parent component (thus, no __INLINE_CODE_227__) and your component is a "Root Cycle", then the sinks are handled to __INLINE_CODE_228__ to "run" as with a regular Cycle.js app.
React __INLINE_CODE_229__
The __INLINE_CODE_230__ed __INLINE_CODE_231__ component is essentially a view for the associated Cycle.js __INLINE_CODE_232__ function. And __INLINE_CODE_233__ encourages the view to be a "dumb" stateless function that takes __INLINE_CODE_234__ as an argument ad returns something of __INLINE_CODE_235__ type (i.e., __INLINE_CODE_236__s, __INLINE_CODE_237__, etc, whatever __INLINE_CODE_238__ component renders).
Note: The wrapped __INLINE_CODE_239__ shouldn't necessarily be a stateless functional component (SFC), you could have any __INLINE_CODE_240__ component wrapped, but its advisable to use functions for the most part as the whole point of __INLINE_CODE_241__ is to use __INLINE_CODE_242__ for anything that is not a view.
So, the __INLINE_CODE_243__ is the only communication channel from our Cycle.js part (the connected component's __INLINE_CODE_244__ function) to connected __INLINE_CODE_245__ component itself. We therefore need to pass the props from our Cycle program to the view (a __INLINE_CODE_246__ component) somehow.
This is where __INLINE_CODE_247__ comes into play and provides __INLINE_CODE_248__ source and sink for a __INLINE_CODE_249__ function. Its local to a component, so no other components will share the same __INLINE_CODE_250__ source or sink.
__INLINE_CODE_251__ sink
The __INLINE_CODE_252__ sink on __INLINE_CODE_253__ed is there to pass the props to the view:
function main(sources) {
return {
props: xs.of({ greetable: 'World' })
};
}
// Use the passed `greetable` prop
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
Remember, that the value you map to __INLINE_CODE_254__ sink should be an object of new props and that updated props will be merged with existing component __INLINE_CODE_255__ like:
const updatedProps = Object.assign({}, prevProps, newProps);
This means that you can easily overwrite any props you want and leave the others intact.
Also, note above that we're passing the __INLINE_CODE_256__ through an __INLINE_CODE_257__ stream and that might be synchronous (like in our example with a stream of static data that is sent immediately) but could also be asynchronous. In that case the value of some prop might be not yet available on the first render:
function main(sources) {
return {
props: xs.of({ greetable: 'World' }).compose(delay(3000))
};
}
// Trying to use the passed `greetable` prop
// But whooops, no `greetable` yet :(
function MyComponent(props) {
return (
<div>
Hello {props.greetable}!
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
So either provide a default value for that async prop's __INLINE_CODE_258__ or perform additional checks in the view, when rendering.
Note: When messing with the __INLINE_CODE_259__ in your __INLINE_CODE_260__, remember that any props that are passed from outside as an input to a __INLINE_CODE_261__ component, are also there (including callback functions, if there are any) and are also merged upon updates from outside (on __INLINE_CODE_262__).
Lets summarize: the __INLINE_CODE_263__ from the __INLINE_CODE_264__ sink are passed from the __INLINE_CODE_265__ function to the view (__INLINE_CODE_266__ component).
__INLINE_CODE_267__ source
Not only you'll need to provide the props to the view for rendering, but oftentimes you'd need your __INLINE_CODE_268__ to take part in transformations/calculations in the __INLINE_CODE_269__ function itself. For that reason, __INLINE_CODE_270__ provides the __INLINE_CODE_271__ source as well.
It allows you to have an __INLINE_CODE_272__ stream of your component's input __INLINE_CODE_273__. Note that the __INLINE_CODE_274__ is of type __INLINE_CODE_275__ and to take the actual stream of props, use its __INLINE_CODE_276__ attribute:
function main(sources) {
const name$ = sources.props.props$.map(props => props.name);
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
function MyComponent(props) {
return (
<div>
Hello, {props.name}! Your father is Mr. {props.father}.
</div>
);
}
const MyConnectedComponent = cycleConnect(main)(MyComponent);
...
// Somewhere else:
<MyConnectedComponent name="Luke Skywalker" father="Darth Vader" />
// renders:
//
// <div>
// Hello, LUKE SKYWALKER! Your father is Mr. Darth Vader.
// </div>
The new value on __INLINE_CODE_277__ is emitted every time __INLINE_CODE_278__ change. This could be overkill for the most cases, as you would only need just few props for your dataflow.
This is what __INLINE_CODE_279__ method for, it accepts the name of the nested prop (will return __INLINE_CODE_280__ if the stream value is not an object) and returns a stream of that prop only that is only got new value once this particular prop changes:
function main(sources) {
const name$ = sources.props.pluck('name');
return {
props: name$.map(name => ({ name: name.toUpperCase() }))
};
}
The __INLINE_CODE_281__ed stream also performs shallow comparison and does __INLINE_CODE_282__ behind the scenes, so identical values don't cause a new value emitted on an __INLINE_CODE_283__.
Note that you don't need to always pull all props from your __INLINE_CODE_284__ function's __INLINE_CODE_285__ just for the sake of it to just forward to the __INLINE_CODE_286__ sink directly, you better only pick those you need in a dataflow with __INLINE_CODE_287__. The __INLINE_CODE_288__ passed from outside will be available on the view normally. You can overwrite them from inside your __INLINE_CODE_289__ though.
There's also __INLINE_CODE_290__ method that also accepts a nested prop name. The difference is that the result is not an __INLINE_CODE_291__ stream of prop values but the __INLINE_CODE_292__ instance itself that is scoped to that piece of __INLINE_CODE_293__:
function main(sources) {
const name$ = sources.props.pluck('name'); // Stream of strings
const nameSource = sources.props.select('someNestedObject'); // Scoped ReactPropsSource
const someNestedObjProperty$ = nameSource.pluck('someNestedObjectProperty');
...
}
This is useful when you want to __INLINE_CODE_294__ a stream of some sub-prop from deeper nested objects.
Note: Taking deep property stream this way isn't really nice currently as requires quite a bit of boilerplate - this part of API is already being thought out further and reconsidered.
Interactions
So far so good, but rendering the __INLINE_CODE_295__ isn't the only exciting thing we want to do with __INLINE_CODE_296__, right? We also want to handle events coming from the view(e.g., the DOM).
The way __INLINE_CODE_297__ handles events and nested components' callbacks is the biggest deviation from an idiomatic Cycle.js app using __INLINE_CODE_298__ driver. It refrains from a holistic "no handlers in the view" approach for a few reasons:
- We need something more in line with __INLINE_CODE_299__ as we're using React here
- We're not always handling DOM events but also need to handle some children components data/events passed back. We can't pass streams like in regular Cycle.js app, in __INLINE_CODE_300__ we do it via callbacks that are provided amongst children components' __INLINE_CODE_301__ and we need __INLINE_CODE_302__ed components to be compatible __INLINE_CODE_303__ components at all levels.
This is what "interactions" feature of __INLINE_CODE_304__ is for. It provides the unified interface for parent-child components communication and event handling that is aligned with a regular event/callback handling model on __INLINE_CODE_305__ side but also allows to access those events in form of __INLINE_CODE_306__ streams inside the __INLINE_CODE_307__ function.
Events triggering by the view
Every __INLINE_CODE_308__ component has some additional __INLINE_CODE_309__-specific props injected, so they are available on your view.
These are __INLINE_CODE_310__ which is a function that takes the __INLINE_CODE_311__ (a string) and optional __INLINE_CODE_312__ (of any type) and returns a function that will be a callback for your event or some child component prop function.
So, the signature is:
(interactionType, predefinedValue?) => (value) => void
Its supposed to be called imperatively in your view and is available as __INLINE_CODE_313__:
function MyComponent({ interact }) {
return (
<div>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
For the sake of being cleaner (and for better TypeScript support), __INLINE_CODE_314__ also leverages the ES2015 __INLINE_CODE_315__ feature to provide the object with dynamic properties, each representing an __INLINE_CODE_316__ with __INLINE_CODE_317__ dynamically defined after the function property name. Its available on __INLINE_CODE_318__ so we can rewrite the above snippet as:
function MyComponent({ interactions }) {
return (
<div>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
In the example above, the __INLINE_CODE_319__ function receives the click __INLINE_CODE_320__ as an argument and that is what you'll get in your __INLINE_CODE_321__ (next section).
While cleaner, it doesn't allow for the __INLINE_CODE_322__ to be set, so if you need some particular value to be emitted by the associated stream in your __INLINE_CODE_323__, you can either use __INLINE_CODE_324__ or create an inline function:
// Assuming you have `id` available, e.g.
// when looping/mapping over the collection of items
<button
type="button"
className="delete-btn"
onClick={interact('delete', id)}
>
Click me!
</button>
// OR
<button
type="button"
className="delete-btn"
onClick={() => interactions.delete(id)}
>
Click me!
</button>
View events handling with __INLINE_CODE_325__ source
There's internal __INLINE_CODE_326__ in __INLINE_CODE_327__ and it provides the __INLINE_CODE_328__ source for our __INLINE_CODE_329__ function. The __INLINE_CODE_330__ source is of type __INLINE_CODE_331__, which has the __INLINE_CODE_332__ method, which we can use to obtain interaction streams:
function main(sources) {
const buttonClick$ = sources.interactions.select('buttonClick');
// Do something with a buttonClick$, e.g.
// map it to the click coordinates props
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interact, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interact('buttonClick')}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
The streams are __INLINE_CODE_333__ed by the same __INLINE_CODE_334__ string you provided to the __INLINE_CODE_335__ function on your view.
Similar to how __INLINE_CODE_336__ object with dynamic function-props is available on your view, __INLINE_CODE_337__ is a ES2015 __INLINE_CODE_338__ and it maintains the dynamic streams right on itself (just don't use __INLINE_CODE_339__ as its reserved method name here here):
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
return {
props: buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
)
};
}
function MyComponent({ interactions, clickX, clickY }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
Note: If you try to get the non-existing stream, it will still work, this thing is dynamic, so the stream will be created nevertheless but will just never emit values. Be careful and look out for typos! Using TypeScript is highly recommended to avoid typos here.
Upstream interactions with __INLINE_CODE_340__ sink (calling callbacks on __INLINE_CODE_341__).
Now that we can handle the streams of view events and values from the children components, we would also need to somehow call the callbacks that are passed among our __INLINE_CODE_342__.
First, nothing prevents from following the common React way and call those directly from the view:
function MyComponent({ onButtonClick }) {
return (
<button type="button" onClick={onButtonClick}>
Click me!
</button>
);
}
// Somewhere else:
doSomething = (event) => { console.log('clicked!'); };
...
<MyComponent onButtonClick={doSomething} />
This is fine if you don't need that value's stream in your dataflow. This approach bypasses your Cycle.js program's __INLINE_CODE_343__ completely and doesn't make it impure by otherwise possibly passing an effectful function as __INLINE_CODE_344__. There's even no need to __INLINE_CODE_345__ your component in the above example.
Keep in mind that if __INLINE_CODE_346__ is optional there, you will also need to check whether that is defined before providing that as a callback (use __INLINE_CODE_347__ or TypeScript to mark it as either required or optional).
Oftentimes you will need to call some callback that lives on the __INLINE_CODE_348__ though, and this is what __INLINE_CODE_349__ sink provided by __INLINE_CODE_350__ does:
function main(sources) {
const buttonClick$ = sources.interactions.buttonClick;
const coords$ = buttonClick$.map(
event => ({
clickX: event.clientX,
clickY: event.clientY,
})
);
// We both update the `props` (to render it in this component)
// and pass upstream
return {
props: coords$,
interactions: xs.of({
onCoordsChange: coords$
})
};
}
function MyComponent({ interactions }) {
return (
<div>
<span>
Last clicked at: X - {clickX}, Y - {clickY}
</span>
<button type="button" onClick={interactions.buttonClick}>
Click me!
</button>
</div>
);
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
handleCoordsChange = (coords) => {
console.log('New coordinates received: ', coords);
};
...
<ConnectedComponent onCoordsChange={handleCoordsChange} />
// The new coords will be rendered by the component
// and the console will show the "New coordinates received"
// with new coords too.
Once the appropriate stream emits a value, that callback is called. It will also check if the callback was passed in the first place to skip the call attempt if no callback with a given name is passed among the __INLINE_CODE_351__.
Important: Note that __INLINE_CODE_352__ sink expects a stream of object whose property names correspond to callback names on __INLINE_CODE_353__ and property values are themselves streams too - whatever values those streams emit, will be passed as an argument to the callback.
So far so good, but we know that our __INLINE_CODE_354__ is a wrapper and not a driver, so what if one passed the effectful function as a callback, e.g., __INLINE_CODE_355__ passed as __INLINE_CODE_356__ above does a __INLINE_CODE_357__ internally - that is a side effect, right?
Well, this is why the function is not invoked directly, or inside the wrapper (since the wrapper is the part of the pure land of our __INLINE_CODE_358__s), its using the __INLINE_CODE_359__ driver internally. More on this below in a section about Side-effect control, just keep in mind that __INLINE_CODE_360__ and __INLINE_CODE_361__ should be pure and __INLINE_CODE_362__ follows that requirement.
React component lifecycle streams
You're probably not going to need __INLINE_CODE_363__ component lifecycle hooks often inside your __INLINE_CODE_364__, but still - they are provided via special __INLINE_CODE_365__. It provides the __INLINE_CODE_366__ source that is available on each __INLINE_CODE_367__ed __INLINE_CODE_368__ sources.
The lifecycle events are provided as __INLINE_CODE_369__ streams and their names mostly mirror the React lifecycle methods, but shortened to remove some redundant __INLINE_CODE_370__ prefixes:
React lifecycle method
__INLINE_CODE_371__ source property
Stream value
__INLINE_CODE_372__
__INLINE_CODE_373__
__INLINE_CODE_374__
__INLINE_CODE_375__
__INLINE_CODE_376__
__INLINE_CODE_377__
__INLINE_CODE_378__
__INLINE_CODE_379__
__INLINE_CODE_380__
__INLINE_CODE_381__
__INLINE_CODE_382__
__INLINE_CODE_383__
__INLINE_CODE_384__
__INLINE_CODE_385__
__INLINE_CODE_386__
__INLINE_CODE_387__
__INLINE_CODE_388__
__INLINE_CODE_389__
__INLINE_CODE_390__
__INLINE_CODE_391__
Error data
The "error data" on the __INLINE_CODE_392__ is an object of this shape:
{
error: Error;
errorInfo: ErrorInfo;
}
These are basically React __INLINE_CODE_393__ arguments squashed into a single object.
Also, note that __INLINE_CODE_394__ hook is not present there, since that is not really an event and affects how your component is rendered. __INLINE_CODE_395__ would be providing its own custom internal __INLINE_CODE_396__ instead for optimisation purposes and you could implement it on your wrapped component anyway.
Note: As well, no lifecycle events are passing anything related to React component __INLINE_CODE_397__, only taking __INLINE_CODE_398__ into account instead. This is because the __INLINE_CODE_399__ is meant to be managed differently when Cycle.js is in use. You can still use that for wrapped component but its out of play on the component generated by __INLINE_CODE_400__.
A quick example:
function main(sources) {
const willUnmount$ = { sources.lifecycle };
// Do something on unmounting, e.g. report that to a console
// via some hypothetical `logger` driver
return {
logger: willUnmount$.mapTo(`I'll be unmounted soon!`)
};
}
Side-effect control and __INLINE_CODE_401__ driver
Side-effect control is where Cycle.js really shines (not only implementation, it applies to the "cycle pattern" behind Cycle.js in the first place).
In order to keep the __INLINE_CODE_402__ part of __INLINE_CODE_403__ed component pure, we need to make sure that any potentially effectful function is not run inside our __INLINE_CODE_404__ directly.
This is what __INLINE_CODE_405__ driver is responsible for. It allows to only describe what function you want to call and with what arguments, then delegate that to a __INLINE_CODE_406__ which is supposed to run any impure functions outside of the app itself.
The way it works is very similar to that of __INLINE_CODE_407__ driver from __INLINE_CODE_408__ package where you don't actually run the request inside your __INLINE_CODE_409__ but just describe it and pass to __INLINE_CODE_410__ driver to handle. __INLINE_CODE_411__ driver is just a bit more generic and operates on functions.
__INLINE_CODE_412__ uses __INLINE_CODE_413__ driver internally (to run effects for upstream __INLINE_CODE_414__), but it is also available for you on any connected __INLINE_CODE_415__ as __INLINE_CODE_416__ sink automatically (i.e., without explicit configuration on a root cycle):
function log() {
console.log(`I'm effectful!`);
}
function saveToLocalStorage(message) {
localStorage.setItem('message', message);
}
function main(sources) {
const someCallbackFn$ = sources.props.pluck('someCallbackFunction');
return {
fnCallEffect: xs.merge(
xs.of({ fn: log }),
xs.of({ fn: saveToLocalStorage, args: ['some message to save'] }),
// Call the `props` callback function directly, this is how
// `interactions` sink works behind the scenes:
someCallbackFn$.map(fn => ({ fn }))
)
};
}
Isolation
Isolation is Cycle.js way of making the component's __INLINE_CODE_417__ and __INLINE_CODE_418__ isolated from influence of other components.
Make sure to carefully read more about Cycle.js isolation here. It might take a bit to get used to an idea.
To reiterate: the concept of isolation is important for Cycle.js apps. Its a little bit different for __INLINE_CODE_419__ because most things are scoped to a particular __INLINE_CODE_420__ed component already, like components have "local" __INLINE_CODE_421__, __INLINE_CODE_422__ and __INLINE_CODE_423__ sources and sinks and __INLINE_CODE_424__ components are also self-contained already.
There are though __INLINE_CODE_425__/__INLINE_CODE_426__ that will need the isolation feature, like __INLINE_CODE_427__ driver or __INLINE_CODE_428__ wrapper (a state management solution for Cycle.js apps). So, isolation makes sense for shared __INLINE_CODE_429__/__INLINE_CODE_430__, those you pass to __INLINE_CODE_431__ function (by defining the __INLINE_CODE_432__/__INLINE_CODE_433__ options on root __INLINE_CODE_434__) and that will be available across the entire tree of Cycle programs.
Note: The isolation works by scoping the source streams to particular component and a kind of unscoping of the sink streams before connecting to parent's sinks.
Also note, that each driver/wrapper decides on its own how its sources/sinks should be isolated.
In Cycle.js, you use the __INLINE_CODE_435__ function from __INLINE_CODE_436__ package directly, as you're just invoking the children __INLINE_CODE_437__ functions from your current component's __INLINE_CODE_438__ function:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// Do something with childSinks, combine somehow, etc
return {
...
};
}
__INLINE_CODE_439__ function has this simple signature:
isolate :: (MainFn, scope) => MainFn
It takes the __INLINE_CODE_440__ function and an optional __INLINE_CODE_441__ and returns an isolated __INLINE_CODE_442__ function.
__INLINE_CODE_443__ does also use the __INLINE_CODE_444__ package internally, but you can't call __INLINE_CODE_445__ directly on some __INLINE_CODE_446__ function. You'd never call one component's __INLINE_CODE_447__ from the other component's __INLINE_CODE_448__ directly with __INLINE_CODE_449__.
Naive explicit isolation (conceptually incorrect)
Just to illustrate the issue, there's an attempt to isolate the component's __INLINE_CODE_450__ sources, so that HTTP requests initiated from that components are scoped to it (and are unavailable to siblings).
Since we know that __INLINE_CODE_451__ returns a __INLINE_CODE_452__ function and __INLINE_CODE_453__ takes the __INLINE_CODE_454__ function as an argument, lets try to feed it an isolated version of it:
function main(sources) {
...
}
function MyComponent() {
...
}
const isolatedMain = isolate(main, { HTTP: 'myComponent' });
const ConnectedComponent = cycleConnect(isolatedMain)(MyComponent);
While this will won't raise any errors (because this is valid code after all), this approach brings a subtle issue.
This will isolate things for this particular components but not their children components, so in this case, children (connected childred __INLINE_CODE_455__ components) will have no __INLINE_CODE_456__ isolation scope for __INLINE_CODE_457__ channel.
This might seems like a no-issue, because hey, you can define different scopes for the children components right? Well, while this is technically possible, its conceptually incorrect and it will be completely broken in some cases, e.g., with __INLINE_CODE_458__ where the deeper you go - the "more filtered" source streams you get.
Important! The above note about it being "conceptually incorrect" refers to the way __INLINE_CODE_459__ maintains the tree of Cycle.js programs internally. It tries to make the entire app structure look the same way as you'd manually write a Cycle.js app. So, when you __INLINE_CODE_460__ at some level, then if inside your __INLINE_CODE_461__ you also call some __INLINE_CODE_462__ happens, then __INLINE_CODE_463__ gets isolated sources no matter what.
__INLINE_CODE_464__ option for __INLINE_CODE_465__
The first way to correctly isolate __INLINE_CODE_466__ed component is to use the __INLINE_CODE_467__ option for __INLINE_CODE_468__ itself, so the above can be rewritten as:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main, {
isolate: { HTTP: 'myComponent' }
})(MyComponent);
This will work perfectly, with all children __INLINE_CODE_469__ed components having __INLINE_CODE_470__ namespace applied to __INLINE_CODE_471__ sources/sinks in their __INLINE_CODE_472__ functions.
The __INLINE_CODE_473__ option accepts a __INLINE_CODE_474__ or the __INLINE_CODE_475__ and in that case its passed directly to __INLINE_CODE_476__ package's __INLINE_CODE_477__ as a __INLINE_CODE_478__ argument. So, check out the __INLINE_CODE_479__ function API reference.
__INLINE_CODE_480__ also makes it accept a function that takes component __INLINE_CODE_481__ as an argument and should return the scope of __INLINE_CODE_482__ or __INLINE_CODE_483__ type that is then also passed to __INLINE_CODE_484__ package's __INLINE_CODE_485__. This allows for some dynamic isolation scopes to be set based on the React component's initial props.
Important: Despite the __INLINE_CODE_486__ is provided as an option to __INLINE_CODE_487__ when defining the connected component, the actual isolation will only happen when component instance is created. This is why its possible to dynamically resolve the __INLINE_CODE_488__ based on the component __INLINE_CODE_489__ in __INLINE_CODE_490__.
__INLINE_CODE_491__ prop
Despite the __INLINE_CODE_492__ option will work nicely for some cases, there's a pattern in how it is utilized in pure Cycle.js programs. Lets take a look at pure Cycle.js example again:
function main(sources) {
const childSinks = isolate(childMain, { DOM: 'foo', HTTP: 'bar' })(sources);
// ...
return {
...
};
}
The important part is that in Cycle.js app its not a child who decides how to isolate itself but __INLINE_CODE_493__ is provided from the parent instead.
Most of the time, we want to maintain the same approach with __INLINE_CODE_494__. The advantage is that component in this case is totally agnostic from the provided scope, it doesn't even care whether its isolated or not at all - all it knows is that it expects certain __INLINE_CODE_495__ and returns certain __INLINE_CODE_496__. This makes the component reusable.
This is especially important with __INLINE_CODE_497__, where you want to control where your child components "attach" to the state.
In other words, the parent decides how to compose children components, not the other way around, because otherwise the child would need to have an intimate knowledge of something that really belongs to parent and thus not being reusable and breaking encapsulation.
For this reason, every __INLINE_CODE_498__ed React component accepts an __INLINE_CODE_499__ prop, so you can use that just like any other prop:
function main(sources) {
...
}
function MyComponent() {
...
}
const ConnectedComponent = cycleConnect(main)(MyComponent);
// Somewhere else:
// Isolate everything
<ConnectedComponent isolate="myComponent" />
// Isolate HTTP only
<ConnectedComponent isolate={{ HTTP: 'myComponent' }} />
<ConnectedComponent isolate={{ HTTP: 'myComponent_2' }} />
In contrast with __INLINE_CODE_500__ option, the __INLINE_CODE_501__ prop can only accept __INLINE_CODE_502__ or __INLINE_CODE_503__ that is directly passed to __INLINE_CODE_504__ function from __INLINE_CODE_505__, thus maintaining full parity with pure Cycle.js approach. Indeed, having a function that accepts initial __INLINE_CODE_506__ wouldn't make much sense because you're anyway providing props that could be different for different component instances when rendering that component.
__INLINE_CODE_507__ helper component
Since isolation in __INLINE_CODE_508__ applies to both "current" __INLINE_CODE_509__ed component and all its __INLINE_CODE_510__ed children, we might sometimes need to define some connected component to only be a common parent for some children, which must provide isolation but wouldn't contain any logic.
This is what a helper __INLINE_CODE_511__ component for. It accepts the __INLINE_CODE_512__ prop (that is directly passed as a __INLINE_CODE_513__ argument to __INLINE_CODE_514__ function of __INLINE_CODE_515__):
<Isolator scope={{ HTTP: 'someNamespace' }}>
<ConnectedComponent someProps="1" />
<ConnectedComponent someProps="2" />
<ConnectedComponent someProps="3" />
</Isolator>
This assists the isolation composition at view level.
Extra: State management with __INLINE_CODE_516__
__INLINE_CODE_517__ is the idiomatic Cycle.js fractal state management solution. It is tailored for Cycle.js apps but since __INLINE_CODE_518__ is using the real Cycle.js, it stays fully compatible here as well.
Using __INLINE_CODE_519__ with __INLINE_CODE_520__ is optional, since you might be using different lib (e.g., __INLINE_CODE_521__) for that.
To start, just read the onionify docs and install the package:
$ npm install --save cycle-onionify
and pass that with __INLINE_CODE_522__ array to a __INLINE_CODE_523__ connected component (e.g., Root Cycle):
// RootCycle.js
import { run } from '@cycle/run';
import onionify from 'cycle-onionify';
import { cycleConnect } from 'react-cycle-connect';
const wrappers = [onionify];
export const RootCycle = cycleConnect({
root: true,
runFn: run,
wrappers
})();
export default RootCycle;
Now every __INLINE_CODE_524__ed component that is a child of the above-mentioned __INLINE_CODE_525__ in React component tree got the __INLINE_CODE_526__ source and sink.
Important: Notice that you're all set - its purely a Cycle.js land. __INLINE_CODE_527__ doesn't provide any additional functionality for __INLINE_CODE_528__. Instead, it provides few helpers for isolation and working with collections on the React side.
All the exportables related to __INLINE_CODE_529__ reside in the extra package: __INLINE_CODE_530__. It is installed along with the main lib but is not __INLINE_CODE_531__d/__INLINE_CODE_532__ed by default, so won't be bundled (by Webpack, Rollup, etc) unless you're using it.
This extra sub-package contains few things:
- __INLINE_CODE_533__ helper component
- __INLINE_CODE_534__ helper component
- __INLINE_CODE_535__ helper factory function
Read ahead for details on each.
In __INLINE_CODE_536__, the state/reducers layer composition is done via isolation, so the first thing here is __INLINE_CODE_537__:
__INLINE_CODE_538__ helper component
This is pretty much like __INLINE_CODE_539__ but is here to specifically isolate __INLINE_CODE_540__ sources/sinks only.
So, the:
import { StateIsolator } from 'react-cycle-connect/lib/extra/onionify';
// ...
<StateIsolator lens="someStateProperty">
<NestedComponent />
</StateIsolator>
is somewhat a convenient shortcut to:
import { Isolator } from 'react-cycle-connect';
// ...
<Isolator scope={{ onion: 'someStateProperty', '*': null }}>
<NestedComponent />
</Isolator>
Notice how __INLINE_CODE_541__'s prop is explicitly named __INLINE_CODE_542__. It just implies that oftentimes you'll be using "lens" to isolate the state. Consider a string just a shortcut to a more elaborate __INLINE_CODE_543__ object.
In the example above, the __INLINE_CODE_544__'s __INLINE_CODE_545__ function __INLINE_CODE_546__ will represent only that "someStateProperty" piece of state. Pretty much how you'd __INLINE_CODE_547__ the __INLINE_CODE_548__ channel in an idiomatic Cycle.js app.
There's also a __INLINE_CODE_549__ property, so that you can change the name of the source/sink inside your __INLINE_CODE_550__ to something else. Thats to be on par with __INLINE_CODE_551__ features, where you can also change that name.
Handling collection state manually
Managing collections of components in pure Cycle.js with __INLINE_CODE_552__ is a kind of a recognized challenge. This is why __INLINE_CODE_553__ ended up providing __INLINE_CODE_554__ helper function - see their docs for more detail.
While __INLINE_CODE_555__ also has a __INLINE_CODE_556__ helper component that is very similar to __INLINE_CODE_557__ __INLINE_CODE_558__ function, lets first try to pretend we don't have it and imagine how'd we handle the state without it.
Note: Nothing prevents you from skipping right to a description of how __INLINE_CODE_559__ works, but it might be useful to understand the rationale behind it, so reading this section is recommended.
Handling collections is different with React than in Cycle.js, e.g., you can always just render React components in a loop or __INLINE_CODE_560__ an array of data to an array of components and React will render that.
When you want to render some __INLINE_CODE_561__ data from your state, its easy without any helpers:
:
{
list: [
{ id: 1, name: 'Obi-Wan Kenobi', kills: 100 },
{ id: 2, name: 'Luke Skywalker', kills: 25 },
{ id: 3, name: 'Darth Vader', kills: 9000 }
]
}
*/
// ListItem.jsx
function ListItem({ itemData }) {
return (
<li>
{itemData.name} (ID: {itemData.id}, kills: {itemData.kills})
</li>
);
}
// List.jsx
function main(sources) {
// Just pass as `props` directly
return {
props: sources.onion.state$
};
}
function List({ list }) {
return (
<ul>
{list.map(item => (
<ListItem key={item.id} itemData={item} />
))}
</ul>
);
}
const ConnectedList = cycleConnect(main)(List);
This keeps your __INLINE_CODE_562__ component just a "dumb" view, there's no even need of connecting it to the Cycle tree. So far so good.
Then you want to add more item-level capabilities, like editing an item (incrementing hero __INLINE_CODE_563__ in our case, for simplicity) or deleting an item. You can still manage the state in the __INLINE_CODE_564__ component, act upon some callbacks for particular item and only stream reducers from inside __INLINE_CODE_565__ component's __INLINE_CODE_566__:
:
{
list: [
{ id: 1, name: 'Obi-Wan Kenobi', kills: 100 },
{ id: 2, name: 'Luke Skywalker', kills: 25 },
{ id: 3, name: 'Darth Vader', kills: 9000 }
]
}
*/
// ListItem.jsx
function ListItem({ itemData, onIncrementClick, onDeleteClick }) {
return (
<li>
{itemData.name} (ID: {itemData.id}, kills: {itemData.kills}) -
<button type="button" onClick={onIncrementClick}>
Increment "kills"
</button>
<button type="button" onClick={onDeleteClick}>
Delete
</button>
</li>
);
}
// List.jsx
function intent(interactions) {
return {
itemIncrement$: interactions.itemIncrementClick,
itemDelete$: interactions.itemDeleteClick
};
}
function model(actions) {
const defaultReducer$ = xs.of(function defaultReducer(state) {
if (typeof state !== 'undefined') return state;
return {
list: []
};
});
const incrementItemReducer$ = actions.itemIncrement$.map(
(id) => function incrementItemReducer(state) {
return {
...state,
list: state.list.map(listItem => {
if (listItem.id !== id) return listItem;
return {
...listItem,
kills: listItem.kills + 1
};
})
};
}
);
const deleteItemReducer$ = actions.itemDelete$.map(
(id) => function deleteItemReducer(state) {
return {
...state,
list: state.list.filter(listItem => listItem.id !== id)
};
}
);
return xs.merge(
defaultReducer$,
incrementItemReducer$,
deleteItemReducer$
);
}
function main(sources) {
const actions = intent(sources.interactions);
const reducer$ = model(actions);
return {
onion: reducer$,
props: sources.onion.state$
};
}
function List({ interactions, list }) {
return (
<ul>
{list.map(item => (
<ListItem
key={item.id}
itemData={item}
onIncrementClick={() => interactions.itemIncrementClick(item.id)}
onDeleteClick={() => interactions.itemDeleteClick(item.id)}
/>
))}
</ul>
);
}
const ConnectedList = cycleConnect(main)(List);
There is already some noise in reducers, especially on updates. Also, what if there's another piece of state sibling to __INLINE_CODE_567__ property? __INLINE_CODE_568__ gets overburdened quickly.
You'd also quickly realize that this makes the collection item state inherently bound to __INLINE_CODE_569__, so while you could be reusing __INLINE_CODE_570__ somewhere else, you couldn't reuse its state management part.
This is unfortunate because certain actions ("edit" and "delete") are related to a single item and can be managed at that item's level. This is totally possible with __INLINE_CODE_571__ because its a fractal state management thing, so we could be taking advantage of that fractal aspect with __INLINE_CODE_572__ too.
So, we'd better be managing internal item state at the __INLINE_CODE_573__ component level. To do that, let's __INLINE_CODE_574__ the __INLINE_CODE_575__ component and make it handle its stuff. We need to carefully let each item's component know which piece of state does belong to it, and we'll use __INLINE_CODE_576__ for that:
value is something like this,
due to isolation on the `List`.
{
id: 1,
name: 'Obi-Wan Kenobi',
kills: 100
}
*/
return {
onion: reducer$,
props: sources.onion.state$.map(state => ({ itemData: state }))
};
}
function ListItem({ interactions, itemData }) {
return (
<li>
{itemData.name} (ID: {itemData.id}, kills: {itemData.kills}) -
<button type="button" onClick={interactions.incrementClick}>
Increment "kills"
</button>
<button type="button" onClick={interactions.deleteClick}>
Delete
</button>
</li>
);
}
const ConnectedListItem = cycleConnect(main)(ListItem);
// List.jsx
function model() {
const defaultReducer$ = xs.of(function defaultReducer(state) {
if (typeof state !== 'undefined') return state;
return {
list: []
};
});
// const addItemReducer$ = ...
return xs.merge(
defaultReducer$,
// addItemReducer$, etc
);
}
function main(sources) {
const reducer$ = model();
return {
onion: reducer$,
props: sources.onion.state$ // Merge state into props
};
}
// A lens for particular item, to extract/update its state correctly
function itemLens(id) {
return {
get(arr) {
if (typeof arr === 'undefined') return void 0;
for (let i = 0, n = arr.length; i < n; ++i) {
if (arr[i].id === id) {
return arr[i];
}
}
return void 0;
},
set(arr, updatedItemState) {
if (typeof arr === 'undefined') {
return [item];
} else if (typeof item === 'undefined') {
return arr.filter((itemState) => itemState.id !== id);
}
return arr.map((itemState) => {
if (itemState.id === id) return updatedItemState;
return itemState;
});
}
};
}
// Note that we no longer need to pass the data to list items,
// they get it from `onion` state directly, on their own, but we still
// need to provide the instructions on how to `isolate` each item state,
// so that each component knows what item from collection it renders.
function List({ interactions, list }) {
return (
<ul>
{list.map(item => (
<ConnectedListItem
key={item.id}
isolate={{ onion: itemLens(item.id) }}
/>
))}
</ul>
);
}
const ConnectedList = cycleConnect(main)(List);
There's another issue with this. Now our listLens is intrinsically bound to a list property on a component state and , because we just know that the ConnectedList component state is of the shape:
{
list: []
}
and item state has an id attribute. But what if List state is:
{
someOtherListProp: []
}
or something else, or has other properties along the way, which we'd need to take into account then:
{
someOtherListProp: [],
somethingElse: 'some string',
someOtherProp: 123,
someNestedObject: { ... }
}
or (unlikely but still possible), the actual collection is an item in some other collection:
{
[
[], // list 1
[], // list 2
[], // list 3
[] // etc
]
}
Also, what if there's another key for collection item and not an id attribute`?
To make item lens reusable, we need it to assume that the collection state is always an Array (or undefined) and pass a function to calculate the key.
Lets refactor this a bit, by extracting the use-case-specific property names out and explicitly isolate our list rendering with <StateIsolator> (unrelated code is omitted):
// ...
// A lens for particular item, to extract/update its state correctly
// Note that we replaced the `id` with the more generic `key` and
// the function now accepts another function as an argument, that
// obtains the "key" to compare.
function itemLens(key, itemKeyFn) {
return {
get(arr) {
if (typeof arr === 'undefined') return void 0;
for (let i = 0, n = arr.length; i < n; ++i) {
if (itemKeyFn(arr[i], i) === key) {
return arr[i];
}
}
return void 0;
},
set(arr, updatedItemState) {
if (typeof arr === 'undefined') {
return [item];
} else if (typeof item === 'undefined') {
return arr.filter(
(itemState, index) => itemKeyFn(itemState, index) !== key
);
}
return arr.map((itemState, index) => {
if (itemKeyFn(itemState, index) === key) {
return updatedItemState;
} else {
return itemState;
}
});
}
};
}
function List({ interactions, list }) {
return (
<ul>
<StateIsolator lens="list">
{list.map(item => (
<ConnectedListItem
key={item.id}
isolate={{ onion: itemLens(item.id, (item, index) => item.id) }}
/>
))}
</StateIsolator>
</ul>
);
}
const ConnectedList = cycleConnect(main)(List);
Alright, this works but wow, there's a lot of boilerplate to write overall for this kind of thing every time, isn't there? We might also be reusing the listLens as its generic enough, so why not have it extracted to a package? Well, thats why we have a <Collection> component that encapsulates the repetitive boilerplate.
<Collection> helper component
This helper component somewhat mirrors the functionality of makeCollection() helper function from cycle-onionify. It allows to avoid the repetitive boilerplate from collection to collection. <Collection> is itself just a cycleConnect()ed component.
It provides two levels of onion source/sink isolation internally - one for "collection" piece of state and then for each rendered item. The collection state isolation is required in order for it to be truly reusable (i.e., we can't just hardcode the <Collection> state to always be some list prop - instead, its made a userland decision, see previous section for a more elaborate description). So, <Collection> state is always expected to be an Array (or undefined).
Collection isolation is configured with a lens prop (read more about lens in cycle-onionify docs):
const fooLens = {
get: (state) => state.foo,
set: (state, childState) => ({ ...state, foo: childState })
};
<Collection lens={fooLens} ... />
For such a straightforward use case (its very common) the string shortcut of the above will suffice:
<Collection lens="foo" ... />
Each item's isolation is implemented internally (with itemLens) and is the core feature of <Collection>.
Note: The code for
itemLensis almost entirely burrowed from thecycle-onionifycodebase - could even probably be reused as is ifcycle-onionifypackageexports that.
You need to specify what component you want rendered as a collection item with itemComponent prop:
<Collection lens="foo" itemComponent={ListItem} />
Note that <Collection> provides isolation for each item and cycleConnect()ed list item is supposed to just pull its data from sources.onion.state$ directly. By default, though, <Collection> also passes the (already correctly isolated) item state as item component's props for convenience and for it to work with non-cycleConnect()ed item components.
Note: Due to architecture of
react-cycle-connect, there are no alternatives tocollectSinksoption andpickMergeandpickCombinehelpers fromcycle-onionify, because sink streams are handled differently inreact-cycle-connectand they always behave as beingpickMerge()d here.
Additional options.
We know that React, when rendering a collection, requires a key prop to be defined on an item component. <Collection> does that for you internally and uses the item's index (position in a list) for that by default. In order to customize the key (used for both React and internal itemLens), you can pass an itemKeyFn prop that accepts a function of this signature:
(itemState, index?) => Key;
Must return a React-compatible Key value. Quick example on how to set that to use item's id attribute:
<Collection
lens="foo"
itemComponent={ListItem}
itemKeyFn={(itemState) => itemState.id}
/>
<Collection>, with the internal use of listLens, provides only onion source/sink isolation for each item by default. If you need other channels to be isolated somehow, you can provide itemIsolate property, which will be just passed as every item's isolate prop. Notr, though, that explicitly setting onion channel scope will be ignored - <Collection> will overwrite that internally.
Next option is channelName which, just like with <StateIsolator> is supposed to change the onion to something else (like stateTree maybe).
Lastly, if you want to tweak the way your item component is rendered, you can pass the itemRender prop to the collection, which needs to be a function compatible with the signature:
(itemState, itemKey, index) => ReactNode
Please note that it completely overrides the item rendering and in this case you're on your own - the itemComponent and itemIsolate props will be ignored, as well as no itemLens will be applied to the item component internally and you'll need to provide the key on your own. This is just an escape hatch and is not supposed to be used often (or probably ever):
<Collection
lens="list"
itemRender={(itemState) => (
<ListItem
{...itemState}
key={itemState.id}
isolate={{ onion: someCustomItemLens(itemState.id) }}
/>
)}
/>
makeFilteredListLens() factory function to assist <Collection>
Alrighty. One more important aspect of lens is that they're useful and are suggested approach for computed props and derived data.
So, the lens prop on <Collection> isn't always supposed to be a shortbut prop name as oftentimes you'd want to show a filtered version of your collection. Since the only responsibility of <Collection> is to just isolate state nicely and render items, filtering the collection data is a userland option.
There are no special props on <Collection> to filter data but since it accepts lens, we can leverage that and provide a lens object that will "zoom" to a filtered subset of our collection data on get and merge things back on set.
You can write that filteredListLens manually, but the use case is so common that there's a helper included into the package. Lets see an example (from our TodoMVC) with "todo" items that also reads a value from state to decide, based on the filter value, which function to use in the predicate:
import { makeFilteredListLens } from 'react-cycle-connect/lib/extra/onionify';
const TODO_FILTER_FUNCTIONS = {
['show_all']: () => true,
['show_active']: (todo) => !todo.completed,
['show_completed']: (todo) => todo.completed
};
const filteredListLens = makeFilteredListLens(
'todos',
(item, state) => TODO_FILTER_FUNCTIONS[state.filter](item),
'id'
);
// Somewhere when rendering:
<Collection lens={filteredListLens} itemComponent={TodoItem} />
The above will only show the filtered subset of the collection and will merge any state changes to the source collection. Please note that this lens is to isolate the collection, not its items - those are still managed internally with some internal itemLens.
The makeFilteredListLens function has this signature:
(stateProp, filterPredicate, equals) => Lens
with all arguments required.
statePropis just a string that defines which piece of state the filtered collection should be calculated from. While it wouldn't be neccessary forgetpart of thelens, we need it forset, because filtered collection is a computed value and doesn't reside on the state directly, so we need to let it know which piece of state is to update.Its going to be the same what you'd otherwise set as
<Collection>lensprop string shortcut, should you want to show an entire collection without filtering.filterPredicateis a function of a signature:(itemState, state) => booleanwhich is the heart of this feature - it decides whether to include or excluded the item into/from the filtered collection. Return
truefor the item to be included (its just a regularArray.prototype.filterbehind the scenes). Particular item state is passed as a first argument but there's also a second argument,state, that is the entirestateobject, where your collection property resides...Wait, WAT! Why would lens know about the state that is beyond of collection state itself? Well, since your state is fractal and you're keeping your list there, chances are you're going to keep the filtering and pagination metadata from that level on as well. This might seems confusing at first, so feel free to check how it's used in our TodoMVC example.
equalsis a predicate to check whether the two items are equal:(itemState1, itemState2) => booleanShould return
trueif two item's states should be considered equal. Its such a common thing to just provide something like(item1, item2) => item1.id === item2.idthat there's a
stringshortcut for it, which accepts the attribute name, e.g.,id:makeFilteredListLens('someStateProps', ..., 'id')
Note that this is only about filtering the state data on isolation, its not about rendering. <Collection> handles rendering no matter what lens is in use and you can also use the created lens on your own, not necessarily with <Collection>.
Testing
This is more or less straightforward here - react-cycle-connect doesn't really have any react-cycle-connect-ish code to write, so it boils down to Cycle.js testing (take a look at @cycle/time here) as well as React testing with, e.g., Jest and Enzyme.
[TODO: To be expanded with examples and practices]
TypeScript support
[WIP]
Quick note: react-cycle-connect is itself written in TypeScript and export lots of types, both internally used and those intended to be used in the userland. Type-annotated examples and suggested practices are coming up!
API reference
[TODO]
Quick note: Guide covers most (if not all) of the public API of the lib, so feel free to check that out in the meantime.
Alternatives / Competitors
cycle-react
This is one of the first attempts to use "Cycle pattern" with React. It doesn't have any references to Cycle.js itself, but it nicely reimplements the pattern, so the architecture is somewhat similar. Its using much more Cycle.js way of handling markup, which you're building inside Cycle program itself there, which isn't the case with react-cycle-connect where there is more explicit boundary between React and Cycle.
Its also where the concept of interactions came from to react-cycle-connect (though, orchestrated differently). Only meant to be used with RxJS 4 and RxJS 5.
Doesn't maintain the tree of Cycle.js programs, focusing on having the "Cycle pattern" at component level instead with a different public API surface.
Looks quite robust in what it does, so definitely take a look at it.
recycle.js
This is a really cool one, another reimplementation of "Cycle pattern" in React, but this time its closer to pure Cycle.js when it comes to event handling with view being a pure readonly representation of state.
The downside is that it explicitly monkey-patches the React.createElement to achieve Cycle.js style of event handling.
Its approach also resembles Elm and hyperapp in that its view, dispatch, update, etc sections are explicitly configured, whereas you just call functions from functions in Cycle.js (and react-cycle-connect).
redux-cycles
Not really an alternative, since it doesn't allow to use Cycle.js for React component dataflow. Its rather a set of drivers for redux, supposed to provide an ability to use Cycle.js for handling side effects in Redux-based app.
Definitely consider it if all you need is managing Redux async actions' side-effects.
Like react-cycle-connect, it assumes the use of the real Cycle.js libs. This means that it could well be a complement to react-cycle-connect if you decide to use Redux as a state container in react-cycle-connect (and not, e.g., cycle-onionify).
FAQ
[TODO]
Roadmap
- Streamline the API
- JSDoc for the entire codebase
- Full test coverage
- Optimize the re-rendering (the entire props update pipeline)
RxJSandmost.jssupport (looking into how Callbags would possibly make their way into Cycle.js, thus making it more reactive-lib agnostic).preactandinfernosupport (and also possiblyVue, in which case it might be renamed somehow)