React &

Flux

Caveat

These notes are based on a spike!

They are not canon.

Spike is not complete.

Links to resources at end.

What's our problem?

Using MVVM scaling complex components becomes hard.

React

The 'V' in MVC

React

  • Makes no assumptions about your stack. Want to use Backbone/Angular, go ahead.
  • Component focused. "The only thing you do is build components."
  • You can easily compose React components.
  • Uses a Virtual DOM to control all DOM access.
  • One way data flow, no observables, no two-way data binding.

Users

  • Atom editor
  • Brackets editor
  • Firefox devtools
  • Facebook/Instagram/NYT

Open sourced for 1 year.

Annoyed some Ember/Backbone core devs.

Angular/Ember looking to copy ideas.

Easy to integrate into existing projects.

Why should we care?

React Properties

  • You re-render the entire component on every change
  • Minimizes state management within your component.
  • You are passed in immutable state (this.props).
  • Only one method declares what the rendered UI should look like (render)

An extension to JS exists whichs allows declarative code within a React component.

/** @jsx React.DOM */

var HelloMessage = React.createClass({
  render: function() {
    return <div>Hello {this.props.name}</div>;
  }
});

React.renderComponent(<HelloMessage name="John" />, mountNode);

JSX: JS + XML

Don't be fooled this is still JS.

Optional.

No more templates.

"Separation of technology not concerns."

render: function() {
    var commentNodes = this.props.data.map(function (comment) {
      return (
        <Comment author={comment.author}>
          {comment.text}
        </Comment>
      );
    });
    return (
      <div className="commentList">
        {commentNodes}
      </div>
    );
  }

Any JS code will work.

So it's a lot more powerful then just templates.

Only one element can be returned from render.

React Properties

  • What happens when you change state is local to the component.
  • Observables aren't listening to properties.
  • You can type check your component inputs (reusable components).

propTypes: {
	initialAmount: React.PropTypes.any.isRequired,
	dealtAsset: React.PropTypes.string.isRequired,
	amountIsValid: React.PropTypes.bool.isRequired,
	amountErrorMessage: React.PropTypes.any
},

There must be state

Sometimes components need internal state

Internal state is accessed via this.state

Internal state is modified via this.setState

/** @jsx React.DOM */
var Timer = React.createClass({
  getInitialState: function() {
    return {secondsElapsed: 0};
  },
  tick: function() {
    this.setState({secondsElapsed: this.state.secondsElapsed + 1});
  },
  componentDidMount: function() {
    this.interval = setInterval(this.tick, 1000);
  },
  render: function() {
    return (
      <div>Seconds Elapsed: {this.state.secondsElapsed}</div>
    );
  }
});

React.renderComponent(<Timer />, mountNode);

After setState

A component and all it's children will be re-rendered

When the data changes, React conceptually hits the "refresh" button.

How can this be done with complex components and not significantly impact performance?

Current user state such as focus or scrolling?

Virtual DOM

  • A JS version of the DOM
  • Nodes within JSX aren't real DOM nodes so the DOM isn't affect by 'render'
  • Allows React to perform virtual DOM diffing
  • Server side rendering

Tree Reconciliation

React compares two virtual DOM trees to find the minimum number of operations to move between the two.

 

  1. React tries to save CPU time by not diffing completely different nodes. So changing the virtual node type will skip the diff.
  2. You can provide unique keys that remain stable between renders. React will prompt you! Makes list rendering more efficient.

Providing keys helps React understand when it's dealing with a different node...

Compose Components

render: function() {
	var TenorLadderRowDate = caplinx.fxexecution.reacttile.components.tenorladder.TenorLadderRowDate;
	var TenorLadderRowButton = caplinx.fxexecution.reacttile.components.tenorladder.TenorLadderRowButton;

	return (
		<div className="tenor-ladder-row">
			<TenorLadderRowButton 
                            ladderRowIndex={this.props.ladderRowIndex} side={'Bid'} />
			<TenorLadderRowDate 
                            ladderRowIndex={this.props.ladderRowIndex} />
			<TenorLadderRowButton 
                            ladderRowIndex={this.props.ladderRowIndex} side={'Ask'} />
		</div>
	);
}

Inside the TenorLadderRow React component.

You can return a tree of components. This is what makes React composable: a key tenet of maintainable frontends.

Component Communications

Data can't just flow downwards.

Users act upon child components.

Sometimes parent components (View Controllers) need to be notified.

ExecuteButton component needs to notify its parent that it's been clicked.

Event handlers

  • Event handlers can call passed in callbacks.
  • Events are standard DOM ones even in IE8.
  • Events do not bubble up to root component.
render: function() {
	return (
		<ExecuteButton 
			onExecute={this._executeTrade}
		/>
	);
},

_executeTrade: function() {
	console.log('Executed');
}
render: function() {
	return (
		<button onClick={this._onClick}>
			{buttonValue}
		</button>
	);
},

_onClick: function() {
	var rateData = {
		...
	};

	this.props.onExecute(rateData);
}

ExecuteButton

TenorLadderRowButton

Is React enough?

If you are building small to medium applications React on it's own might be enough.

In larger applications where you need to share data between many React components though...

Data has to be migrated upwards until a shared ancestor is found that can pass data back down.

Yuck.

I want those minutes of my life back.

Flux

Application Architecture for Building User Interfaces

...is more of a pattern than a framework

Flux...

Flux applications have three major parts: the dispatcher, the stores, and the views

Flux only allows unidirectional data flow.

It's a half-duplex communication system.

Data flow cannot loop back around.

Utilities

They are the data model layer.

 

They create, maintain and dispose of subscriptions to server side data.


TenorLadderRowUtility.prototype._onSettlementDateUpdate = function(assetPair, settlementDate) {
	this._tenorLadderRowActionCreator.settlementDateReceived(settlementDate);
};

To know how they should control subscriptions they need to listen to the dispatcher.

ActionCreators

ActionCreators provide a semantic dispatcher API.

Facilitate passing data to the dispatcher.

They pass data to the dispatcher in the form of an action.

Actions are the only concept lacking their own class/module. They are simple object literals containing the action data and type.


TenorLadderRowActionCreator.prototype.settlementDateReceived = function(settlementDate) {
	this._dispatcher.dispatch({
		type: ActionTypes.TENOR_ROW_SETTLEMENT_DATE_RECEIVED,
		settlementDate: settlementDate
	});
};

TenorLadderRowActionCreator

Dispatcher

Is essentially a global event bus that

  1. You cannot specify what to listen to.
  2. You can control the order of execution of event handlers.

You dispatch data via action creators and all listeners registered with the dispatcher are notified.

Stores

The domain model of the application. They contain the application's data and business logic.

 

They register with the Dispatcher.

Accept updates and reconcile them as appropriate.

 

They have no mutator methods.

You can only get state from them.

Stores

They are Event Emitters.

When their state changes they emit an event.

Listeners who are interested in the Store state can then retrieve it.

Views

React components.

Expect with less downward flowing data.

 

The Stores help us out of our scaling problem!

 

React components can now register with the Stores to be notified of domain model changes.

From the top

  1. A settlement date update is received by the utility.
  2. The utility calls the action creator to notify it of the update.
  3. The action creator creates an action and passes it into the dispatcher.
  4. The dispatcher notifies all stores with the action.
  5. The stores handle the update if the action type is of interest.
  6. Any store that changes its state raises an event.
  7. Views registered with the store update themselves.

The other direction

Well it's not too different.

The only difference is it's not a utility calling the action creator.

The action creator is called by the view.

From the user

  1. The user interacts with the view.
  2. The view calls the action creator to notify it of the user interaction.
  3. The action creator creates an action and passes it into the dispatcher.
  4. The dispatcher notifies all stores with the action.
  5. The stores handle the update if the action type is of interest.
  6. Any store that changes its state raises an event.
  7. Views registered with the store update themselves.

There is one issue with "classical" flux.

Once all the flux actors in an application are wired up data flow is simple.

But how do you wire them all up?

MAKE THEM ALL SINGLETONS.


var ChatAppDispatcher = require('../dispatcher/ChatAppDispatcher');
var ChatWebAPIUtils = require('../utils/ChatWebAPIUtils');
var MessageStore = require('../stores/MessageStore');

module.exports = {

  createMessage: function(text) {
    ChatAppDispatcher.handleViewAction({
      type: ActionTypes.CREATE_MESSAGE,
      text: text
    });
    var message = MessageStore.getCreatedMessageData(text);
    ChatWebAPIUtils.createMessage(message);
  }

};

ChatMessageActionCreators

Flux example

So we need some pattern for dealing with creating objects.

Instances per Component instance

Factories


class TileDependenciesFactory {

	constructor(dispatcher) {}

	registerActionCreator(actionCreatorName, actionCreatorClass) {}

	registerUtility(utilityName, utilityClass) {}

	registerStore(storeName, storeClass) {}

	getActionCreator(actionCreatorName) {}

	getUtility(utilityName) {}

	getStore(storeName) {}

	getDispatcher {}

}

The react components are passed this in as a prop.

Factory passed into react components.


TileComponent.prototype.onOpen = function() {
	var tileDispatcher = new Flux.Dispatcher();

	this._tileDependenciesFactory = new TileDependenciesFactory(tileDispatcher);
	this._populateFactory();

	React.renderComponent(
		<Tile factory={this._tileDependenciesFactory} />,
		this._mountNode
	);
};

TileComponent.prototype._populateFactory = function() {
	this._tileDependenciesFactory.registerActionCreator('TenorLadderRow', TenorLadderRowActionCreator);

	this._tileDependenciesFactory.registerUtility('TenorLadderRow', TenorLadderRowUtility);

	this._tileDependenciesFactory.registerStore('TenorLadderRowDate', TenorLadderRowDateStore);
	this._tileDependenciesFactory.registerStore('TenorLadderRowButton', TenorLadderRowButtonStore);
};

	componentWillMount: function() {
		this.props.factory.getStore('TenorLadderRowButton').addChangeListener(this._onChange, this);
	},

	_onChange: function() {
		this.setState(this._getStore().getState());
	}

Factory allows wiring up without static references to classes.

Could allow clients to customise components easily.

Deserializing

Our components can be persisted.

 

This means our stores will have to return serialized data on initial construction.

How do we get that data into every store being used in a component?

 

So when a view is first rendered it gets the serialized state from the store and not some default initial state.

We represented component state as an object literal


tile = {
	amount: 500,
	dealtAsset: 'GBP',
	assetPair: 'GBPUSD',
	instrument: '/FX/GBPUSD',
	tenorladder: [
		{
			date: {
				tenor: "SPOT"
			}
		}
	]
}

Cursors

"way to manage a component's focus on just the state data that it needs to operate."

 

So when a store is created it's passed in a cursor that has a reference to the state that the store should be able to mutate.

Our additions

  1. Factory. We no longer depend on singletons and it could allow configuration of components
  2. Cursors. Serialize/deserialize support also allows to experiment with undo/redo.
  3. Scoped dispatchers and actions. Reduces the amount of traffic through the components dispatcher, debatable if needed.

Purpose

"Purpose of this spike is the find out whether React provides sufficient productivity and performance gains to replace the existing Presenter/Knockout MVVM solution."

  1. Understandable ?
  2. Debuggable
  3. Integrates with data model (trade, permissions, prices)
  4. Modular
  5. Swappable
  6. Extensible for customers
  1. All tests fast and reliable (not via DOM)
  2. Unit testable
  3. AT's suitable for business owners
  1. Price update latency
  2. Render time (e.g. creating 10 new tiles)
  3. CPU on high update rate
  4. Trade on right price under high load
  1. Community support
  2. Expected lifespan
  3. Ease of upgrade
  4. Core maintainers likelihood to stick around
  5. Our customers will understand it

Subjective

I would use flux/react over current competitors*

 

No real Ember or Angular experience.

References

React & Flux

By briandipalma

React & Flux

Presentation on React & Flux & an FxTile spike using them.

  • 2,684