reactnative

Why to use Rx with React?

linkitesreact

At first sight RxJS is blown up lodash but for dealing also with async. In reality it’s so much more than that. With a few simple operators you can implement Redux-like state machine, schedule animation or deal with any type of events no matter whether it’s WebSocket message or filling in the text input.

Angular 2 is armed with RxJS out of the box but how about its great competitor?We at Linkites like React so much not only for what it is but also for that what you can make of it. we’ve(Linkites) been asked couple times by our clients how to integrate RxJS into an existing app.

Reactive state

Redux does the job and it’s way simpler than its precursor. In real live app written with Flux has many stores but it leads to unnecessary complexity and managing state dependencies. Redux with single store gained popularity really quickly. How about no store at all? With reactive state we don’t need passive store. We can replace createStore with createState. Observable state will update components calling setState, or better, with props.

// createState

function createState(reducer$, initialState$ = Rx.Observable.of({})) {
  return initialState$
    .merge(reducer$)
    .scan((state, [scope, reducer]) =>
      ({ ...state, [scope]: reducer(state[scope]) }))
    .publishReplay(1)
    .refCount();
}

Thanks to calling publishReplay, refCount we’ll share the same state among all observers. In RxJS 4 you can achieve the same with shareReplay.

test("createState creates reactive state using scoped reducers", (t) => {
  const add$ = new Rx.Subject();
  const counterReducer$ = add$.map(payload => state => state + payload);
  const rootReducer$ = counterReducer$.map(counter => ["counter", counter]);
  const state$ = createState(rootReducer$, Rx.Observable.of({ counter: 10 }));

  t.plan(1);

  add$.next(1); // No subscribers yet(//Linkites)

  state$.toArray().subscribe((results) => {
    t.deepEqual(results, [{ counter: 10 }, { counter: 12 }]);
  });

  add$.next(2);
  add$.complete();
});

Actions, ActionCreators, Constants…

How about just actions? Subjects actually. Subject is both at the same time, observable and observer.

// counterActions

import { createActions } from "../state/RxState";
export default createActions(["increment$", "decrement$", "reset$"]);

// which is the same as

export default {
  increment$: new Rx.Subject,
  decrement$: new Rx.Subject,
  reset$: new Rx.Subject,
};


Normally it wouldn’t be the best choice to drop the idea of this separation. We can take advantage of consuming observables and using simple map operator and later change action… info reducer. This eliminates the need for constants and tests… I don’t like the idea of testing basically syntax. Do you?

Reducer($)

This part actually is going to be significantly different from what you already know about reducers. In Redux reducers are pure functions which return new state if they can handle particular actions.

// CounterReducer

import Rx from "rxjs";
import counterActions from "../actions/counterActions";

const initialState = 0;

const CounterReducer$ = Rx.Observable.of(() => initialState)
  .merge(
    counterActions.increment$.map(payload => state => state + payload),
    counterActions.decrement$.map(payload => state => state - payload),
    counterActions.reset$.map(_payload => _state => initialState),
  );

export default CounterReducer$;

Reducers, reducer$ actually, is a stream of lazy-evaluated, pure functions. We can take data carried by an action and change it into single, pure function.
import test from "ava";
import { pipe } from "ramda";
import counterActions from "../actions/counterActions";
import CounterReducer$ from "./CounterReducer";

test("handles increment, decrement and reset actions", (t) => {
  CounterReducer$.take(5).toArray().subscribe((fns) => {
    t.is(pipe(...fns)(), 9);
  });

  counterActions.increment$.next(1);
  counterActions.reset$.next();
  counterActions.increment$.next(10);
  counterActions.decrement$.next(1);
});

As you can see testing such reducer is really simple with little ramda help.
 

connect

Firstly, we don’t want to tight coupling components with any modules. Components props can be treated as a simple, but still powerful, dependency injection. The question is how to extract action creators (action subject in our case) so the implementation details wont leak.

Redux already solved this problem with connect from react-redux. Our implementation will be simpler but yet fully functional for our reactive use case.

 

// connect

function connect(selector = state => state) {
  return function wrapWithConnect(WrappedComponent) {
    return class Connect extends Component {
      static contextTypes = {
        state$: PropTypes.object.isRequired,
      };

      componentWillMount() {
        this.subscription = this.context.state$.map(selector).subscribe(::this.setState);
      }

      componentWillUnmount() {
        this.subscription.unsubscribe();
      }

      render() {
        return (
          <WrappedComponent {...this.state} {...this.props} />
        );
      }
    };
  };
}

 

Connect component is wrapping component to pass state to props. selector is deadly simple but it has second, not so obvious usage. Use selector also for passing actions as props. There is no need to make it another argument when we don’t have to call dispatch.

test("connect maps state to props in RxStateProvider context", (t) => {
  const add$ = new Rx.Subject();
  const counterReducer$ = add$.map(payload => state => state + payload);
  const rootReducer$ = counterReducer$.map(counter => ["counter", counter]);
  const state$ = createState(rootReducer$, Rx.Observable.of({ counter: 10 }));

  const Counter = ({ counter, add }) => (
    <div>
      <h1>{counter}</h1>
      <button onClick={add}>add</button>
    </div>
  );

  const ConnectedCounter = connect(state => ({
    counter: state.counter,
    add: () => add$.next(1),
  }))(Counter);

  const tree = mount(
    <RxStateProvider state$={state$}>
      <ConnectedCounter />
    </RxStateProvider>
  );

  t.is(tree.find("h1").text(), "10");
  tree.find("button").simulate("click");
  t.is(tree.find("h1").text(), "11");
});

Reactive components

import React, { PropTypes } from "react";
import { connect } from "../state/RxState";
import counterActions from "../actions/counterActions";

export const Counter = props => (
  <div>
    <h1>{props.counter}</h1>
    <hr />
    <button onClick={() => props.increment(1)} id="increment">+</button>
    <button onClick={() => props.increment(10)} id="increment10">+10</button>
    <button onClick={props.reset} id="reset">Reset</button>
    <button onClick={() => props.decrement(1)} id="decrement">-</button>
    <button onClick={() => props.decrement(10)} id="decrement10">-10</button>
  </div>
);

Counter.propTypes = {
  counter: PropTypes.number.isRequired,
  increment: PropTypes.func.isRequired,
  decrement: PropTypes.func.isRequired,
  reset: PropTypes.func.isRequired,
};

export default connect(state => ({
  counter: state.counter,
  reset() { counterActions.reset$.next(); },
  increment(n) { counterActions.increment$.next(n); },
  decrement(n) { counterActions.decrement$.next(n); },
}))(Counter);
We wrapped Counter in higher-order components. The main advantage is that it then can be a “dumb” component which can be treated like a pure function. Pure functions are highly reusable, easy to test and maintain.

import React from "react";
import test from "ava";
import sinon from "sinon";
import { shallow } from "enzyme";
import { Counter } from "./Counter";

test("displays counter", (t) => {
  const increment = sinon.spy();
  const decrement = sinon.spy();
  const reset = sinon.spy();
  const tree = shallow(
    <Counter
      counter={123}
      increment={increment}
      decrement={decrement}
      reset={reset}
    />
  );
  t.is(tree.find("h1").text(), "123");
});

test("calls passed actions", (t) => {
  const increment = sinon.spy();
  const decrement = sinon.spy();
  const reset = sinon.spy();
  const tree = shallow(
    <Counter
      counter={123}
      increment={increment}
      decrement={decrement}
      reset={reset}
    />
  );
  tree.find("#increment").simulate("click");
  t.true(increment.calledWith(1));
  tree.find("#increment10").simulate("click");
  t.true(increment.calledWith(10));
  tree.find("#reset").simulate("click");
  t.true(reset.called);
  tree.find("#decrement").simulate("click");
  t.true(decrement.calledWith(1));
  tree.find("#decrement10").simulate("click");
  t.true(decrement.calledWith(10));
});

Async

How to handle AJAX calls? According to good practices from Flux or Redux the initial call to the server starts in the action creator. What do we do when we don’t have action creators? We can init the request in the reducer. While it doesn’t sound great take into consideration that we are dealing with one stream and time is irrelevant.

UserActions.fetch$.flatMap(userId => {
  return Rx.Observable.ajax(`/users/${userId}`)
});
Observables have many advantages over Promises and one of the greatest is the ability to retry.
UserActions.fetch$.concatMap(userId => {
  return Rx.Observable.ajax(`/users/${userId}`)
    .retryWhen(err$ => err$.delay(1000).take(10));
});

Summary

As you can see React can be easily used along with other libraries. This is great.

We have only scratched the surface of possible RxJS use cases in React. With even basic knowledge about RxJS observables and operators you can do pretty awesome stuff in just couple lines of code. Linkites have been working on react , reactRx , Reactive , React Native , Material UI from last 2 years and has 150+ dedicated developers on Rx and React and we proudly say that ‘Linkites is the best React and RX development company in India’

for more details please write us at- info@linkites.com

 

 
Likes(2)Dislikes(0)
Share:
FacebookTwitterGoogle+LinkedInTumblr

Leave a Reply

Your email address will not be published. Required fields are marked *