Git Product home page Git Product logo

rx-react's Introduction

RxReact

ReactJS bindings for RxJS

Installation

Install this module with npm:

npm install rx-react

Usage:

RxReact provides a set of utilities to work with RxJS and React :

  • The StateStreamMixin
  • The LifecycleMixin
  • The PropsMixin
  • The RxReact.Component base class
  • The FuncSubject helper

StateStreamMixin

The StateStreamMixin allows to bind a component state to an RxJS Observable stream. The way to achieve the binding is to provide a getStateStream method on your component that returns an RxJS Observable, the StateStream mixin will automatically merge the state of your component with the values published by the returned observable. The subscription will be automaticly cleaned on component unmount.

Example:

var StateStreamMixin = require('rx-react').StateStreamMixin;
var React = require('react');
var Rx = require('rx');


var Timer = React.createClass({
  mixins: [StateStreamMixin],
  getStateStream: function () {
    return Rx.Observable.interval(1000).map(function (interval) {
      return {
        secondsElapsed: interval
      };
    });
  },
  render: function () {
    var secondsElapsed = this.state ? this.state.secondsElapsed : 0;
    return (
      <div>Seconds Elapsed: {secondsElapsed}</div>
    );
  }
});

React.render(<Timer />, document.getElementById('timer-holder'));

LifecycleMixin

The LifecycleMixin allows you to consume React components lifecycle events as RxJS Observable. The LifecycleMixin will inject a property lifecycle to the component, that property contains an observable for each lifecycle events.

Example :

var LifecycleMixin = require('rx-react').LifecycleMixin;
var React = require('react');
var Rx = require('rx');


var Component = React.createClass({
  mixins: [LifecycleMixin],
  componentWillMount: function () {
    this.lifecycle.componentDidMount.subscribe(function () {
      console.log('componentDidMount');
    });
    
    this.lifecycle.componentWillReceiveProps.subscribe(function (props) {
      console.log('componentWillReceiveProps : ' JSON.stringify(props));
    });
    
    this.lifecycle.componentWillUpdate.subscribe(function ({nextProps, nextState}) {
      console.log('componentWillUpdate : ' JSON.stringify({nextProps, nextState}));
    });
    
    this.lifecycle.componentDidUpdate.subscribe(function ({prevProps, prevState}) {
      console.log('componentDidUpdate : ' JSON.stringify({prevProps, prevState}));
    });
    this.lifecycle.componentWillUnmount.subscribe(function () {
      console.log('componentWillUnmount');
    });
  },
  render: function() {
    //...
  }
});

PropsMixin

The PropsMixin allows to obtain a stream of props as RxJS Observable for your component. Example :

var PropsMixin = require('rx-react').PropsMixin;
var React = require('react');


var Component = React.createClass({
  mixins: [PropsMixin],
  componentWillMount: function () {
    this.propsStream.subscribe(function (props) {
      console.log(props.message);
    }
  },
  render: function() {
    //...
  }
});

var comp = React.render(<Component message='Hello World!' />, domNode); // log 'Hello World!'
comp.setProps({message: 'Hello John'}); // log 'Hello John'

This is particulary useful in combination with the StateStreamMixin when your component states depends on Props.

Component

The RxReact.Component is a base class combining the behavior of the PropsStreamMixin and the StateStreamMixin. It extends React.Component. Example:

var RxReact = require('rx-react');
var Rx = require('rx');

class MyComponent extends RxReact.Component {
  getStateStream() {
    return Rx.Observable.interval(1000).map(function (interval) {
      return {
        secondsElapsed: interval
      };
    });
  }
  
  render() {
    var secondsElapsed = this.state ? this.state.secondsElapsed : 0;
    return (
      <div>Seconds Elapsed: {secondsElapsed}</div>
    );
  }
}

Note that when you extend lifecycle methods, you must call the super method.

Before the 0.3.x versions RxReact.Component also implemented lifecyle mixin behavior, for some perf reasons and because most of the time it's unnecessary this has been removed. If you want reenable this behavior use FuncSubject as lifecycle method, or manually apply the LifecycleMixin on your class.

FuncSubject

The FuncSubject helper allows to create an RxJS Observable that can be injected as callback for React event handlers, refs, etc... To create an handler use the create function of FuncSubject

var myHandler = FuncSubject.create()

Example:

var FuncSubject = require('rx-react').FuncSubject;
var React = require('react');
var Rx = require('rx');


var Button = React.createClass({
  componentWillMount: function () {
    this.buttonClicked = FuncSubject.create();
    
    this.buttonClicked.subscribe(function (event) {
      alert('button clicked');
    })
  },
  render: function() {
    return <button onClick={this.buttonClicked} />
  }
});

FuncSubject also accept a function as argument, if provided this funtion will be used to map the value of each elements. This function will always be called even if the FuncSubject has no subscription.

var FuncSubject = require('rx-react').FuncSubject;
var React = require('react');
var Rx = require('rx');


var MyComponent = React.createClass({
  componentWillMount: function () {
    this.inputValue = FuncSubject.create(function (event) {
      return event.target.value
    });
    
    this.inputValue.subscribe(function (value) {
      alert('inputValue changed :' + value);
    })
  },
  render: function() {
    return <input onChange={this.inputValue} />
  }
});

FuncSubject.behavior

You can also create a FuncSubject that extends BehaviorSubject. simply use the behavior function exposed by FuncSubject:

var subject = FuncSubject.behavior(intialValue, mapFunction)

FuncSubject.async

You can also create a FuncSubject that extends AsyncSubject. simply use the async function exposed by FuncSubject:

var subject = FuncSubject.async(mapFunction)

FuncSubject.replay

You can also create a FuncSubject that extends ReplaySubject. simply use the replay function exposed by FuncSubject:

var subject = FuncSubject.replay(bufferSize, mapFunction)

FuncSubject.replay

You can create a FuncSubject from any subject base class using the factory function expsed by FuncSubject:

var subject = FuncSubject.factory(SubjectClass, mapFunction, ...constructorArgs);

rx-react's People

Contributors

austinpray avatar davidnpma avatar derflocki avatar fdecampredon avatar frederickfogerty avatar geowa4 avatar morenoh149 avatar yamalight avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rx-react's Issues

Use rx-lite instead of rx

Nearly all of my projects use rx-lite these days since it was published to npm. Is there any reason we can't use rx-lite? I've run a quick spike on my fork to see if it's possible - all tests pass when changed over to rx-lite.

If you are ok with this change and there are no adverse affects I can submit a PR.

Webpack: Cannot resolve module 'rx-react'

Hi,

I'm getting this error in my Webpack build:
Module not found: Error: Cannot resolve module 'rx-react' in [...]

Do I need to specify anything in my webpack config for rx-react to be included?

What advantages does Rx bring to the normal Callback React flow?

Hey mate!

I'm genuinely interested in Rx and i'm just starting out learning more about Reactive Extenstions, FRP and other techniques (csp, channels).

And since we've spoken before about react related matters i'd just like to ask you what Rx brings to the table in an application based on React + flux.

Cheers mate,
Anders

Dependency removed in 0.14

Module not found: Error: Cannot resolve module 'react/lib/invariant' in /Users/.../node_modules/rx-react/lib

Support for RxJS 5 (beta)

Any plans to support RxJS 5 ?
I'm considering forking this repo to provide a RxJS 5 compatible version.

RxReact.createClass

New Factory for RxReact Components

In 0.3 I'll introduce a new way of creating RxReact components trough some sort of factory.
The goal is to provide a more 'Rx' compatible way of working with react, less based on OOP concept, and more based on pure function and Observable Composition.
A very early implementation has landed in master just now, and you can see an example here.

However I'm still hesitant between 2 different API , since a piece of code is a lot more clear that my poor english explanations, here are 2 very basic todolist implemented with both API.

Higher Order Function

'use strict';

const React = require('react');
const Rx = require('rx');
const RxReact = require('rx-react');
const ReactDOM = require('react-dom');

class TodoList {
  render() {
    const { items } = this.props;
    return (
      <ul>{items.map((itemText, index) => 
        <li key={index + itemText}>{itemText}</li>
      )}</ul>
    );
  }
}

const TodoApp = RxReact.createClass(function TodoApp() {

  const onChange = RxReact.FuncSubject.create();

  const handleSubmit = RxReact.FuncSubject.create(function (e) {
    e.preventDefault();
  });

  const inputValueStream = (
    onChange
    .map(e => e.target.value)
    .startWith('')
  );

  const itemsStream = (
      handleSubmit
      .withLatestFrom(inputValueStream, (_, text) => text )
      .scan((items, text) => items.concat(text), [])
      .startWith([])
  );

  const textStream = inputValueStream.merge(handleSubmit.map(''));

  const stateStream = Rx.Observable.combineLatest(
    textStream,
    itemsStream,
    (text, items) => ({ text, items })
  );

  function render({state: {items, text}}) {
    return (
      <div>
        <h3>TODO</h3>
        <TodoList items={items} />
        <form onSubmit={handleSubmit}>
          <input onChange={onChange} value={text} />
          <button>{'Add #' + (items.length + 1)}</button>
        </form>
      </div>
    );
  }
  return {render, stateStream};
});

ReactDOM.render(<TodoApp />, document.getElementById('container'));

Declared events

'use strict';

const React = require('react');
const Rx = require('rx');
const RxReact = require('rx-react');
const ReactDOM = require('react-dom');

class TodoList {
  render() {
    const { items } = this.props;
    return (
      <ul>{items.map((itemText, index) => 
        <li key={index + itemText}>{itemText}</li>
      )}</ul>
    );
  }
}

const TodoApp = RxReact.createClass({
  displayName: 'TodoApp',

  events: () => ({
    onChange: RxReact.FuncSubject.create(),
    handleSubmit: RxReact.FuncSubject.create(e => e.preventDefault())
  }),

  getStateStream({ events: {onChange, handleSubmit } }) {
    const inputValueStream = (
      onChange
      .map(e => e.target.value)
      .startWith('')
    );

    const itemsStream = (
        handleSubmit
        .withLatestFrom(inputValueStream, (_, text) => text )
        .scan((items, text) => items.concat(text), [])
        .startWith([])
    );

    const textStream = inputValueStream.merge(handleSubmit.map(''));

    return Rx.Observable.combineLatest(
      textStream,
      itemsStream,
      (text, items) => ({ text, items })
    );
  },


  render({state: {items, text}, events: {onChange, handleSubmit}}) {
    return (
      <div>
        <h3>TODO</h3>
        <TodoList items={items} />
        <form onSubmit={handleSubmit}>
          <input onChange={onChange} value={text} />
          <button>{'Add #' + (items.length + 1)}</button>
        </form>
      </div>
    );
  }
});

ReactDOM.render(<TodoApp />, document.getElementById('container'));

While the first API seems a lot more simple and create less constraints for the developper; the second one could greatly improve testability: One could simply test the different parts of the spec object instead of the whole component.
However it creates some problems when you want to access state of the components inside of an event handler.
It would certainly require some extra works/magic inside RxReact to handle this problem (like passing props/state as argument of the events handler like for the Returning state pattern described in react-future), and I'm not really sure it worths the complexity.

Lifecycle for RxReact.Component

I'm trying to extend Component as well as use FuncSubject. The issue is that Component overrides componentWillMount where I'd like to create the FuncSubject. Any tips on what to do? Building on the comment tutorial from React's documentation, I've illustrated my present workaround below. I'd be happy to submit any PRs, but I figured I'd ask first.

export class CommentComponent extends RxReact.Component {
  getStateStream () {
    this.makeComment = RxReact.FuncSubject.create();
    this.makeComment
      .forEach(makeComment);

    return commentListStream.map(list => {
      return { data: list }
    });
  }

  render () {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.makeComment} />
      </div>
    );
  }
}

Rewriting react examples with ES6 classes

What do you think about rewriting the docs using ES6 classes? createClass will be deprecated eventually (as you can see here) so maybe it's a good idea to stop using it in the docs.
If you like the idea I can send a PR :)

Object.assign has been deprecated in React 15.0.0

React has deprecated the use of Object.assign in the latest version, which makes this library crash.

A quickfix would be changing it to require('module-assign'). I will be creating a PR with this modification, but I still fear it'll break its usage in previous React versions.

Still, if you encounter this problem, hopefully you can use this Fork to work with 15.0.0.

How to add falcor?

can you give directions on how you would go about adding falcor, please

Question on FuncSubject and StateStreamMixin

I'm hoping this is a silly question, but I'm having trouble combining FuncSubject and StateStreamMixin when not using ES6 classes (I have other mixins I want to keep using for now). I'm sure the problem is something to do with timing/lifecycle, but I haven't been able to figure it out.

The (overly simple) sample below creates a simple component that should update it's state based on a text input, but I can't figure out how to make my observable available to getStateStream (I've tried making it in componentWillMount with no success, and even getInitialState). It appears that getStateStream is being called before componentWillMount, so the Observable isn't set yet.

Any help would be appreciated, and thanks so much for this library!

var React = require('react');
var Rx = require('rx');
var RxReact = require('rx-react');

var Comp2 = React.createClass({
    mixins: [RxReact.StateStreamMixin],

    componentWillMount: function() {
        this.aChangeHappened= RxReact.FuncSubject.create();
    },

    getStateStream: function() {
        return this.aChangeHappened.map(function(e) {
            return {value: e.target.value}
        });
    },

    render: function() {
        console.log(this.state);
        return (
            <div>
                <h1>Header</h1>
                <input onChange={this.aChangeHappened} />
            </div>
        );
    }
});

Open to including ES7 decorators as an alternative to throwing everything in componentWillMount?

This library looks fantastic; though I wonder if including ES7 decorators would make digesting components a little easier (as Babel supports them). Here's what I mean...

// instead of this
var Button = React.createClass({
  componentWillMount: function () {
    this.buttonClicked = FuncSubject.create();

    this.buttonClicked.subscribe(function (event) {
      alert('button clicked');
    })
  },
  render: function() {
    return <button onClick={this.buttonClicked} />
  }
});

// do this
var Button = React.createClass({
  @stream
  buttonClicked (eventStream) {
    eventStream.subscribe(function (event) {
      alert('button clicked');
    });
  },

  render: function() {
    return <button onClick={this.buttonClicked} />
  }
});

If this is a good idea, I will submit a PR for it. As far as I can tell it's just a small wrapper around the FuncSubject you've already written. ๐Ÿ˜„

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.