Leverage Existing Libraries
in React.js

@garryyao

EF Education First

React is pretty, but my app have already relied on existing libraries

React is all around JSX and Virtual DOM, 
but I don't like opinionated thing!

Templates were used exclusively
for view logics, it's hard to rewrite them

User interactions now relies a lot on
DOM manipulationand
jQuery plugins have been doing good 

I use an exiting fetching/store module shipped 
with the current MVC framework

Agenda

  • How to integrate DOM mutations libraries
  • How to integrate HTML Template
  • How to integrate with own widget system

React is simply a rendering engine in the clothes of an tremendous opinionated framework

React is misunderstood

React.render( ReactElement element, DOMElement container, [function callback] )

Meet the simplest React, that's it!

Let's recall some memories...

insin/DOMBuilder

Provides a convenient, declarative API for generating HTML elements, via objects which contain functions named for the HTML element they create

The DOMBuilder API

with(DOMBuilder.dom) {
  var article =
    DIV({'class': 'article'}
    , H2('Article title')
    , P('Paragraph one')
    , P('Paragraph two')
    )
}

The Desugared JSX

React.DOM.div({className: "article"}, 
  React.DOM.h2(null, "Article title"), 
  React.DOM.p(null, "Paragraph one"), 
  React.DOM.p(null, "Paragraph two")
)

Matt-Esch/virtual-dom

A JavaScript DOM model supporting element creation, diff computation and patch operations for efficient re-rendering
// 1: Create a function that declares what the DOM should look like
function render(count)  {
    return dom('div', {
        style: {
            lineHeight: (100 + count) + 'px'
        }
    }, [String(count)]);
}

// 2: Initialise the document
var count = 0;      // We need some app data. Here we just store a count.

var tree = render(count);               // We need an initial tree
var rootNode = createElement(tree);     // Create an initial root DOM node ...
document.body.appendChild(rootNode);    // ... and it should be in the document

// 3: Wire up the update logic
setInterval(function () {
      count++;

      var newTree = render(count);
      var patches = diff(tree, newTree);
      rootNode = patch(rootNode, patches);
      tree = newTree;
}, 1000);

Now that you know React isn't something new, it is a revamped implementation

A React ticker component

/** @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);
  },
  componentWillUnmount: function() {
    clearInterval(this.interval);
  },
  render: function() {
    return (
      <div>Seconds Elapsed: {this.state.secondsElapsed}</div>
    );
  }
});

React.render(<Timer />, document.body);

A mounted React ticker component

<body>
 <div data-reactid=".0">
  <span data-reactid=".0.0">Seconds Elapsed: </span>
  <span data-reactid=".0.1">14</span>
 </div>
</body>

React track DOM element by data-reactid

<body>
 <div data-reactid=".0">
  <span data-reactid=".0.0">Seconds Elapsed: </span>
  <span data-reactid=".0.1">14</span>
 </div>
</body>

React only update the DOM node that is binded to an expression value

React.createElement("div", 
 null, // DOM attributes is static
 "Seconds Elapsed: ", // first text node child is static
 this.state.secondsElapsed // second text node child is an expression
)

 first text node child of .0.1 (14) is the only child that React will touch in each render cycle

<body>
 <div data-reactid=".0">
  <span data-reactid=".0.0">Seconds Elapsed: </span>
  <span data-reactid=".0.1">14</span>
 </div>
</body>

You can make any DOM change you like, as soon as the element with ".0.1" is still online

<body>
 <div data-reactid=".0">
  <span data-reactid=".0.0">Seconds Elapsed: </span>
  <span data-reactid=".0.1">14</span>
 </div>
</body>

componentDidMount is invoked once immediately after the initial rendering occurs. At this point in the lifecycle, the component has a DOM representation which you can access via React.findDOMNode(this).

componentDidUpdate is the invoked each time after the component's updates are flushed to the DOM. (This method is not called for the initial render)

Now let the integration begins...

Init your jQuery plugin on componentDidMount

Update your jQuery plugin on componentDidUpdate

Query your data store or send ajax requests on componentDidMount

Add in JS/CSS animation on

 componentWill(Did)Update

Trigger custom change events of your data models on

 componentWillReceiveProps

Teardown your view/controller object on

 componentWillUnmount

That's all great stuff, but what if I already have view logic written in handlebars templates?

You just need to adapt $.fn.html to React.render that gives you reactively rendering

Handlebars -> HTML -> .html()

Handlebars -> HTML -> $el.html()

Handlebars -> HTML -> Virtual DOM
React.render(React.dom.div(...), $el.get(0), callback)

  • ReactElement
  • ReactNode
  • ReactComponent

So what exactly can be rendered by React? 

The primary type in React used to present DOM element, now support all HTML5 and SVG element

ReactElement

var child = React.createElement('li', null, 'Text Content');
var root = React.createElement('ul', { className: 'my-list' }, child);
React.render(root, document.getElementById('example'));

The markup you created in JSX will be compiled as ReactElement

ReactElement

var root = <ul className="my-list">
             <li>Text Content</li>
           </ul>;
React.render(root, document.getElementById('example'));
  • ReactElement
  • string (aka. ReactText)
  • number (aka. ReactText)
  • Array of ReactNodes (aka. ReactFragment)

ReactNode

It shall be either one of the following types:

  • <Calendar>
  • <App>
  • <Router>
  • <Transition>

ReactComponent

The Custom UI component in React

Why React Component?

  • Widget composition
  • State encapsulation
  • life-cycle events

But I already have my own widgets system, React Component cannot co-exist with my components

Case Study: TroopJS + React

  • TroopJS: A declarative widget system
  • TroopJS: A declarative widget system
Widget.extend({
  "sig/start": function() {
    this.num = 0;
    this.query('/api').then(function(data) {
      return this.signal("render", data);
    })
  }, 
  "sig/render": function(data) {
    var tpl = '...';
    return this.html(tpl(data));
  }, 
  "dom:button/click": function() {
    var changes = '...';
    this.command('/api', changes);
  }
});

TroopJS widget APIs

  • Lifecycle events, we call it signals - 'sig/start'
  • Declarative DOM event handlers - 'dom:button/click'
  • Object data query language - query('github.repos')
  • jQuery .html + DOM template to render states

Our ideal widget

  • Lifecycle events, we call it signals - 'sig/start'
  • Declarative DOM event handlers - 'dom:button/click'
  • Object data query language - query('github.repos')
  • jQuery .html + DOM template to render states
  • React and JSX for rendering
  • React.setState for internal state changes
  • All other ReactComponent goodness...

What about a hybrid widget

  • Given a widget "specification object"
  • Group properties into two group, for TroopJS and React correspondingly
  • Create the "first half" with Widget.extend of TroopJS
  • Mix-in the "another half" into the created Widget
  • Send this hybrid widget to React.createClass
  • You get a ReactComponent with all legacy APIs!

We end up with combining the goodness from both worlds...

Widget.extend({
  "sig/start": function() {
    this.num = 0;
    this.query('/api').then(function(data) {
      this.setState(data);
    }.bind(this));
  },
  render: function() {
    return (
      <div className="awesome">
        <p>{this.state.num}</p>
      </div>
    );
  },
  "dom:button/click": function() {
    var changes = '...';
    this.command('/api', changes).then(function(newData) {
      this.setState(newData);
    }.bind(this));
  },
  componentDidMount: function() { /*...*/ },
  componentDidUpdate: function() { /*...*/ },
  componentWillReceiveProps: function() { /*...*/ },
  componentDidUpdate: function() { /*...*/ },
});

Rollup

  • Integrate DOM mutations with Virtual DOM
  • Integrate any transition applied to component state update
  • Integrate existing widget with ReactComponent APIs

Q & A

react-leverage-existing

By Garry Yao

react-leverage-existing

How do you leverage existing libraries in React

  • 2,488