react-streamable

A small library for accessing React component events as functional reactive streams


Keywords
React, kefir, component, stream, reactive, streamable, functional, FRP, rx
License
MIT
Install
npm install react-streamable@0.1.2

Documentation

THIS PROJECT IS DEPRECATED

A new project has been created, observe-component that has an updated/more semantic API and will continue to be developed. The goal is to grow to become as library-agnostic as possible, to promote components-as-observables in all environments.

react-streamable

import React from 'react';
import {render} from 'react-dom';
import {streamComponent, fromComponent} from 'react-streamable';

const StreamableButton = streamComponent('button', ['onClick']);

function MyButton(props) {
    return (<StreamableButton>Hello</StreamableButton>);
}

render(<MyButton />, Document.getElementById('my-app'));

const clickStream =
    fromComponent(StreamableButton)
    .onValue(() => {
        console.log('world!');
    });

API

streamComponent(Component, events[])

Returns a higher-order StreamableComponent with an attached stream of the specified events. Supports all events supported by React's event system.

Example:

const StreamingDiv =
    streamComponent('div', ['onMouseDown', 'onMouseUp']);

fromComponent(StreamableComponent, [ events[] ])

Returns the stream attached to the StreamableComponent. An optional array of events can be supplied to return a stream only containing those events.

Example:

const StreamingDiv = streamComponent('div', ['onMouseDown', 'onMouseUp']);

// will log all 'onMouseDown' and 'onMouseUp' events
fromComponent(StreamingDiv).log()

// will only log 'onMouseUp' events
fromComponent(StreamingDiv, ['onMouseUp']).log();

But why?

Because Functional Reactive Programming is pretty cool, and so is React. However, React's API is not very FRP-friendly; the necessity to wire up events by hand using buses (or subjects, in RxJS-speak) easily leads us to the Bus of Doom, and in general is finnicky and boilerplate-y to connect an observer to React.

There are also plenty of libraries for connecting streams to React, but very few (none that I've found) that transition React events to streams, enabling a fully functional reactive architecture.

Dependencies

At the moment, react-streamable depends directly on Kefir for streams. There is no reason for this. The library could easibly be ported to RxJS/Bacon.js/Fairmont/whatever. Under the hood, it uses Kefir's pool object (basically an equivalent to RxJS' Subject, or Bacon's Bus) to abstract the events into streams; we really never escape the bus, we just hide it. I'm interested in trying to create a portable version that can work with any reactive programming library.

More examples

A slightly more complex example

import React, {Component} from 'react';
import {render} from 'react-dom';
import {streamComponent, fromComponent} from 'react-streamable';

const StreamableInput = streamComponent('input', ['onChange']);

class MyApp extends Component {
    render() {
        return (
            <div>
                <div>Hello {this.props.name}!</div>
                <StreamableInput type="text" />
            </div>
        );
    }
}

const nameStream =
    fromComponent(StreamableInput)
    /* The streams values contain two properties:
        'event': The name of the event that was triggered, e.g. 'onChange'
        'e': The React SyntheticEvent
    */
    .map(({event, e}) => e.target.value)
    .onValue((name) => 
        render(<MyApp name={name} />, document.getElementById('my-app'))
    );

You can stream any component

...as long as you pass event handlers to the appropriate elements. The library simply passes special handlers to React's event system (on<Event>) to abstract them into one stream.

function MyWidget(props) {
    return (
        <div>
            <button onClick={props.onClick}>Click me!</button>
            <input onChange={props.onChange} defaultValue="Change me!" />
        </div>
    );
}

const StreamableWidget = streamComponent(MyWidget, ['onClick', 'onChange']);
const widgetStream = 
    fromComponent(StreamableWidget)
    .onValue(({event, e}) => {
        if (event === 'onClick') {
            console.log('clicked');
        }
        else if (event === 'onChange') {
            console.log('changed: '+e.target.value);
        }
    });

However, you are strongly encouraged to create streams out of basic components and merge them, rather than manually pass the event handlers yourself.

import {merge} from 'kefir';

const StreamableButton = streamComponent('button', ['onClick']);
const StreamableInput = streamComponent('input', ['onChange']);

function MyWidget(props) {
    return (
        <div>
            <StreamableButton>Click me!</StreamableButton>
            <StreamableInput defaultValue="Change me!" />
        </div>
    );
}

const widgetStream = 
    merge([
        fromComponent(StreamableButton),
        fromComponent(StreamableInput),
    ])
    .onValue(({event, e}) => {
        if (event === 'onClick') {
            console.log('clicked');
        }
        else if (event === 'onChange') {
            console.log('changed: '+e.target.value);
        }
    });

License

MIT