npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@cycle/react

v2.10.0

Published

Utilities to interoperate between Cycle.js and React

Downloads

168

Readme

Cycle React

Interoperability layer between Cycle.js and React

  • Use React (DOM or Native) as the rendering library in a Cycle.js app
  • Convert a Cycle.js app into a React component
  • Support model-view-intent architecture with isolation scopes
npm install @cycle/react

Example

import xs from 'xstream';
import {render} from 'react-dom';
import {h, makeComponent} from '@cycle/react';

function main(sources) {
  const inc = Symbol();
  const inc$ = sources.react.select(inc).events('click');

  const count$ = inc$.fold(count => count + 1, 0);

  const vdom$ = count$.map(i =>
    h('div', [
      h('h1', `Counter: ${i}`),
      h('button', {sel: inc}, 'Increment'),
    ]),
  );

  return {
    react: vdom$,
  };
}

const App = makeComponent(main);

render(h(App), document.getElementById('app'));

Other examples:

Read also the announcement blog post.

Usage

Install the package:

npm install @cycle/react

Note that this package only supports React 16.4.0 and above. Also, as usual with Cycle.js apps, you might need xstream (or another stream library).

Use the hyperscript h function (from this library) to create streams of ReactElements:

import xs from 'xstream'
import {h} from '@cycle/react'

function main(sources) {
  const vdom$ = xs.periodic(1000).map(i =>
    h('div', [
      h('h1', `Hello ${i + 1} times`)
    ])
  );

  return {
    react: vdom$,
  }
}

Alternatively, you can also use JSX or createElement:

import xs from 'xstream'

function main(sources) {
  const vdom$ = xs.periodic(1000).map(i =>
    <div>
      <h1>Hello ${i + 1} times</h1>
    </div>
  );

  return {
    react: vdom$,
  }
}

However, to attach event listeners in model-view-intent style, you must use h which supports the special prop sel. See the next section.

Use hyperscript h and pass a sel as a prop. sel means "selector" and it's special like ref and key are: it does not affect the rendered DOM elements. Then, use that selector in sources.react.select(_).events(_):

import xs from 'xstream'
import {h} from '@cycle/react'

function main(sources) {
  const increment$ = sources.react.select('inc').events('click')

  const count$ = increment$.fold(count => count + 1, 0)

  const vdom$ = count$.map(x =>
    h('div', [
      h('h1', `Counter: ${x}`),
      h('button', {sel: 'inc'}),
    ])
  )

  return {
    react: vdom$,
  }
}

The sel can be a string or a symbol. We recommend using symbols to avoid string typos and have safer guarantees when using multiple selectors in your Cycle.js app.

Use hyperscript h and pass a sel as a prop. Use that selector in sources.react.select(sel).events(whatever) to have cyclejs/react pass an onWhatever function to the react component:

import React from "react";
import ReactDOM from "react-dom";
import { makeComponent, h } from "@cycle/react";

// React component
function Welcome(props) {
  return (
    <div>
      <h1>Hello, {props.name}</h1>
      <button onClick={() => props.onPressWelcomeButton({ random: Math.random().toFixed(2) }) } >
        press me
      </button>
    </div>
  );
}

// Cycle.js component that uses the React component above
function main(sources) {
  const click$ = sources.react
    .select('welcome')
    .events('pressWelcomeButton')
    .debug('btn')
    .startWith(null);

  const vdom$ = click$.map(click =>
    h('div', [
      h(Welcome, { sel: 'welcome', name: 'madame' }),
      h('h3', [`button click event stream: ${click}`])
    ])
  );

  return {
    react: vdom$
  };
}

const Component = makeComponent(main);
ReactDOM.render(<Component />, document.getElementById('root'));

This library supports isolation with @cycle/isolate, so that you can prevent components from selecting into each other even if they use the same string sel. Selectors just need to be unique within an isolation scope.

import xs from 'xstream'
import isolate from '@cycle/isolate'
import {h} from '@cycle/react'

function child(sources) {
  const elem$ = xs.of(
    h('h1', {sel: 'foo'}, 'click$ will NOT select this')
  )
  return { react: vdom$ }
}

function parent(sources) {
  const childSinks = isolate(child, 'childScope')(sources)

  const click$ = sources.react.select('foo').events('click')

  const elem$ = childSinks.react.map(childElem =>
    h('div', [
      childElem,
      h('h1', {sel: 'foo'}, `click$ will select this`),
    ])
  )

  return { react: elem$ }
}

Use makeComponent which takes the Cycle.js main function and a drivers object and returns a React component.

const CycleApp = makeComponent(main, {
  HTTP: makeHTTPDriver(),
  history: makeHistoryDriver(),
});

Then you can use CycleApp in a larger React app, e.g. in JSX <CycleApp/>. Any props that you pass to this component will be available as sources.react.props() which returns a stream of props.

If you are not using any other drivers, then you do not need to pass the second argument:

const CycleApp = makeComponent(main);

Besides makeComponent, this library also provides the makeCycleReactComponent(run) API which is more powerful and can support more use cases.

It takes one argument, a run function which should set up and execute your application, and return three things: source, sink, (optionally:) events object, and dispose function.

  • run: () => {source, sink, events, dispose}

As an example usage:

const CycleApp = makeCycleReactComponent(() => {
  const reactDriver = (sink) => new ReactSource();
  const program = setup(main, {...drivers, react: reactDriver});
  const source = program.sources.react;
  const sink = program.sinks.react;
  const events = {...program.sinks};
  delete events.react;
  for (let name in events) if (name in drivers) delete events[name];
  const dispose = program.run();
  return {source, sink, events, dispose};
});

source is an instance of ReactSource from this library, provided to the main so that events can be selected in the intent.

sink is the stream of ReactElements your main creates, which should be rendered in the component we're creating.

events is a subset of the sinks, and contains streams that describe events that can be listened by the parent component of the CycleApp component. For instance, the stream events.save will emit events that the parent component can listen by passing the prop onSave to CycleApp component. This events object is optional, you do not need to create it if this component does not bubble events up to the parent.

dispose is a function () => void that runs any other disposal logic you want to happen on componentWillUnmount. This is optional.

Use this API to customize how instances of the returned component will use shared resources like non-rendering drivers. See recipes below.

Use the shortcut API makeComponent which is implemented in terms of the more the powerful makeCycleReactComponent API:

import {setup} from '@cycle/run';

function makeComponent(main, drivers, channel = 'react') {
  return makeCycleReactComponent(() => {
    const program = setup(main, {...drivers, [channel]: () => new ReactSource()});
    const source = program.sources[channel];
    const sink = program.sinks[channel];
    const events = {...program.sinks};
    delete events[channel];
    for (let name in events) if (name in drivers) delete events[name];
    const dispose = program.run();
    return {source, sink, dispose};
  });
}

Assuming you have an engine created with setupReusable (from @cycle/run), use the makeCycleReactComponent API like below:

function makeComponentReusing(main, engine, channel = 'react') {
  return makeCycleReactComponent(() => {
    const source = new ReactSource();
    const sources = {...engine.sources, [channel]: source};
    const sinks = main(sources);
    const sink = sinks[channel];
    const events = {...sinks};
    delete events[channel];
    const dispose = engine.run(sinks);
    return {source, sink, dispose};
  });
}

Use the makeCycleReactComponent API like below:

function fromSourceSink(source, sink) {
  return makeCycleReactComponent(() => ({source, sink}));
}

See @cycle/react-dom.

See @cycle/react-native.

License

MIT, Copyright Andre 'Staltz' Medeiros 2018