Git Product home page Git Product logo

reaxjs's Introduction

ReaxJS

npm version

React + rxjs

Installation

Install using npm/yarn

npm install --save reaxjs

Basic example

Here is a small example using ReaxJS

import React from 'react';

import reax from 'reaxjs';

const Demo = reax({
  input: event => event.target.value
},
({input}) => ({
  output: input.pipe(
    map(x => x.toUpperCase()).
    startWith('')
  )
}),
(values, events) => (
  <div>
    <input onChange={events.input} />
    <span>{values.output}</span>
  </div>
));

ReactDOM.render(<Demo>, document.body);

Ok, lets break it down. The example above renders as an input and a span, where the span always shows the upper-case text from the input. This can be made into a pure component. This pure component takes a prop object with a values object and an events object. The values object has one property, output, while the events object has one function property, input.

//PureDemo.js
export default function PureDemo(props){
  return (
    <div>
      <input onChange={props.events.input} />
      <span>{props.values.output}</span>
    </div>
  );
}

This pure component can be wrapped with a stateful react component or Redux. Writing pure state-less components is a good habit. But we need to store the state someplace, and we can use rxjs to implicitly store the state:

function toUpperCase(inputObservable){
  return inputObservable.pipe(
    map(x => x.toUpperCase()).
    startWith('')
  );
}

This function takes an observable as parameter and returns an observable. It will upper-case the values it receives from the inputObservable, and it will ensure it always has a vaule by returning the empty string.

We can use reax to combine the pure component and the toUpperCase function.

const Component = reax(eventsToValues, observablesFactory, (values, events) => <PureDemo values={values} events={events} />);

The first parameter to reax is an object of functions, each function mapping from an event to a value. The events object used in the PureDemo component has the same keys as this object. When you call one of the functions on the events, then the corresponding function on eventsToValues will be called. These functions are used to convert react events to values. For the PureDemo component it should look like this:

const events = {
  'input': event => event.target.value,
  // add more events here with the format name: event => value
};

The second parameter is a factory function that produces an object of observables. It should have a parameter called for example events. The events parameter to the factory has the same keys as the events object we created earlier, but the values are observables, not functions. The stream of values the observable produces is whatever the function with the same name in the events object returns. The observablesFactory should return an object, where each property is an observable. Since we have already made the toUpperCase function (which, remember, returns an observable), we can define it like this:

function observablesFactory(events){
  return {
    output: toUpperCase(events.input),
    // add more observables here
  };
}

Each returned observable can use one or more events, and can use the props for initial values and parameters.

Now that we have events and the observablesFactory we can combine them with PureDemo using reax:

reax(events, observablesFactory, (values, events) => <PureDemo values={values} events={events} />);

//which is the same as

reax({
  'input': event => event.target.value
},
(events, props) => ({
  output: toUpperCase(events.input)
}),
(values, events) => (
  <div>
    <input onChange={events.input} />
    <span>{values.output}</span>
  </div>
));

reax can be partially applied by ommiting the last parameter (the component), so it can also be used as a decorator:

@reax(events, observablesFactory)
class PureDemo extends React.Component{
  render(){
    return (
      <div>
        <input onChange={this.props.events.input} />
        <span>{this.props.values.output}</span>
      </div>
    );
  }
}

Props

Props passed to the reaxed component will be forwarded to the pure component, and will be passed into the observablesFactory method as an observable. This way you can react to prop changes using RxJS. As an example, consider a component that can be incremented/decremented, with a prop to control how much to increment/decrement by:

function observablesFactory(events, props){
  return {
    sum: Rx.Observable.merge(
      events.increment,
      events.decrement
    ).pipe(
      withLatestFrom(props, (dir, {delta}) => dir*delta),
      scan((sum, delta) => sum+delta, 0)
    )
  };
}

//...

<Counter delta={1} />

If you are only interested in the initial props values (ie, they should never change), then you can use the third parameter:

function observablesFactory(events, props, initalProps){
  return {
    sum: Rx.Observable.merge(
      events.increment,
      events.decrement
    ).pipe(
      scan((sum, delta) => sum+delta, initialProps.initial)
    )
  }
}

//...

<Counter initial={0} />

TypeScript

ReaxJS is written in TypeScript, and works very well with type inference. Specify the input type of each eventMappings parameter (and optionally the Props, if you need them), and TypeScript will figure out the rest:

import * as React from 'react';
import * as Rx from 'rxjs/Rx';
import reax, {constant} from 'reaxjs';

export interface Props {
  readonly initalValue : number
}

export default reax({
  increment: constant(+1),
  decrement: constant(-1),
}, (events, props, initalProps : Props) => ({
  sum: Rx.Observable.merge(
    events.increment,
    events.decrement
  ).pipe(
    scan((sum, delta) => sum+delta, initalProps.initalValue)
  )
}), (values, events, props) => (
  <div>
    <button onClick={events.decrement}>-</button>
    <span>{values.sum}</span>
    <button onClick={events.increment}>+</button>
  </div>
));

In this example the constant(value) function is used. It lets you disregard the type of the event and set it to always return a constant value. This is useful for example for button clicks, where the contents of the event isn't useful. In the above example increment will always return +1 and decrement will always return -1. You can also use constant() without passing in any argument, in which case it will always return true.

Inspiration

This is based on my experience working with react, redux and react-most. There are, in my opinion, a few problems with them:

  • Redux is not async, so doing anything with network requires other libraries.
  • Redux stores everything in a global shared store, so having multiple similar components requires more work.
  • React-most doesn't use rxjs by default.
  • React-most pushes every event through the same observable, and uses stringly typed keys and switch statements, instead of the power of observables.

ReaxJS is my attempt to solve all of these issues.

reaxjs's People

Contributors

mariusgundersen avatar

Watchers

 avatar  avatar

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.