JavaScript technologies

React

Horváth, Győző
senior lecturer
horvath.gyozo@inf.elte.hu

Financed from the financial support ELTE won from the Higher Education Restructuring Fund of the Hungarian Government

React

View (and DOM) abstraction

First impression: Hello world in React

Tools and code

npm install --save react react-dom
browserify -t babelify main.js -o bundle.js
<!doctype html><title>React</title>
<div id="container"></div>
<script src="dist/bundle.js"></script>
// main.js
var React = require('react');
var ReactDOM = require('react-dom');

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('container')
);

React

  • not a framework
  • library for creating UI elements
    • render + events
  • the View in MVC
  • can be used in other frameworks
    • Backbone, Angular, Ember

Basic concepts

  • Components
  • Rerender on every modification
  • Virtual DOM

Components

var TodoList = React.createClass({
  render: function() {
    var createItem = function(itemText, index) {
      return <li key={index + itemText}>{itemText}</li>;
    };
    return <ul>{this.props.items.map(createItem)}</ul>;
  }
});
var TodoApp = React.createClass({
  getInitialState: function() {
    return {items: [], text: ''};
  },
  onChange: function(e) {
    this.setState({text: e.target.value});
  },
  handleSubmit: function(e) {
    e.preventDefault();
    var nextItems = this.state.items.concat([this.state.text]);
    var nextText = '';
    this.setState({items: nextItems, text: nextText});
  },
  render: function() {
    return (
      <div>
        <h3>TODO</h3>
        <TodoList items={this.state.items} />
        <form onSubmit={this.handleSubmit}>
          <input onChange={this.onChange} value={this.state.text} />
          <button>{'Add #' + (this.state.items.length + 1)}</button>
        </form>
      </div>
    );
  }
});

Source --------------------------

Separation of concerns

  • reduce coupling (the explicit dependencies between modules)
  • increase cohesion (the degree to which elements of a module belong together)

Separation of concerns

Formerly these were separated:

  • Templates
  • ViewModel (display logic)
  • Controller

But they belong together.

Former approach separated technologies, not concerns.

React components

  • Divide up the UI to functional units
  • Description of a display unit
  • Template + display logic
  • Component hierarchy
var TodoApp = React.createClass({
  getInitialState: function() {
    return {items: [], text: ''};
  },
  onChange: function(e) {
    this.setState({text: e.target.value});
  },
  handleSubmit: function(e) {
    e.preventDefault();
    var nextItems = this.state.items.concat([this.state.text]);
    var nextText = '';
    this.setState({items: nextItems, text: nextText});
  },
  render: function() {
    return (
      <div>
        <h3>TODO</h3>
        <TodoList items={this.state.items} />
        <form onSubmit={this.handleSubmit}>
          <input onChange={this.onChange} value={this.state.text} />
          <button>{'Add #' + (this.state.items.length + 1)}</button>
        </form>
      </div>
    );
  }
});

Component

  • composable
  • reusable
  • testable

Creating and displaying elements

JavaScript:

ReactDOM.render(
  React.createElement('h1', null, 'Hello, world!'),
  document.getElementById('example')
);

JSX: alternate format (via Babel)

ReactDOM.render(
    <h1>Hello, world!</h1>,
    document.getElementById('example')
);

Creating a component

ES5:

var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
});
ReactDOM.render(
  <CommentBox />,
  document.getElementById('example')
);

Creating a component

ES6:

class HelloMessage extends React.Component {
  render() {
    return <div>Hello world!</div>;
  }
}
ReactDOM.render(<HelloMessage />, mountNode);

Properties

Data coming from outside of a component

this.props

class HelloMessage extends React.Component {
  render() {
    return <div>Hello {this.props.name}</div>;
  }
}
ReactDOM.render(<HelloMessage name="Győző" />, mountNode);

State

Inner data

this.state, this.setState()

var LikeButton = React.createClass({
  getInitialState: function() {
    return {liked: false};
  },
  handleClick: function(event) {
    this.setState({liked: !this.state.liked});
  },
  render: function() {
    var text = this.state.liked ? 'like' : 'haven\'t liked';
    return (
      <p onClick={this.handleClick}>
        You {text} this. Click to toggle.
      </p>
    );
  }
});

Type of components

  • stateful
  • stateless
    • like idempotent functions

Event handling

  • synthetic events
  • W3C consistent
  • automatic delegation
var LikeButton = React.createClass({
  getInitialState: function() {
    return {liked: false};
  },
  handleClick: function(event) {
    this.setState({liked: !this.state.liked});
  },
  render: function() {
    var text = this.state.liked ? 'like' : 'haven\'t liked';
    return (
      <p onClick={this.handleClick}>
        You {text} this. Click to toggle.
      </p>
    );
  }
});

Accessing the DOM

  • getDOMNode()
  • ReactDOM.findDOMNode()

Component life-cycle events

  • Mounting
    • getInitialState()
    • componentWillMount()
    • componentDidMount()
  • Updating
    • componentWillReceiveProps(object nextProps)
    • shouldComponentUpdate(object nextProps, object nextState)
    • componentWillUpdate(object nextProps, object nextState)
    • componentDidUpdate(object prevProps, object prevState)
  • Unmounting
    • componentWillUnmount()

Other features

  • key: identifying DOM nodes
  • ref: accessing DOM nodes

Rendering

Essential concepts

Not modifying, re-rendering!

Re-render the whole component hierarchy on every data change.

Like PHP.

How can this be effective?

Virtual DOM

Essential concept

  • DOM is slow
  • --> In-memory DOM representation
  • DOM diff --> minimal set of DOM mutations
  • Batched and optimal updates in the real DOM
  • Works on server-side

Thinking in React

Based on this article

1. step

Decomposing the UI to components

  • identifying
  • organizing a hierarchy

2. step

Writing a static version

  • using state is prohibited
  • only render() methods

3. step

Identifying a minimal UI state

  • DRY (Don't Repeat Yourself)
  • Three questions:
    • coming from props: not state
    • Changing with time? If no, not state
    • Can be computed? If yes, not state

4. step

Determining the location of the state

  • On every state the following questions should be asked:
    • identify every component that render based on that state
    • search for their common ancestor
    • This or its ancestor will hold the state
    • If we cannot find the place in either component, make a new component above all other components, and put the state into that one
  • getInitialState()

5. step

Data Down, Actions Up

  • ensure the way of actions through the component hierarchy
  • (ReactLink addon)

Changing pages

  • Own implementation
  • Using React-Router

A router example

var router = (function () {

    "use strict";

    var routes = [];

    function addRoute(route, handler) {

        routes.push({parts: route.split('/'), handler: handler});
    }

    function load(route) {
        window.location.hash = route;
    }

    function start() {

        var path = window.location.hash.substr(1),
            parts = path.split('/'),
            partsLength = parts.length;

        for (var i = 0; i < routes.length; i++) {
            var route = routes[i];
            if (route.parts.length === partsLength) {
                var params = [];
                for (var j = 0; j < partsLength; j++) {
                    if (route.parts[j].substr(0, 1) === ':') {
                        params.push(parts[j]);
                    } else if (route.parts[j] !== parts[j]) {
                        break;
                    }
                }
                if (j === partsLength) {
                    route.handler.apply(undefined, params);
                    return;
                }
            }
        }
    }

    window.onhashchange = start;

    return {
        addRoute: addRoute,
        load: load,
        start: start
    };

}());

Source

An example of page state handling

var App = React.createClass({
    getInitialState: function() {
        return {
            page: null
        }
    },
    componentDidMount: function() {
        var self = this;
        router.addRoute('', function() {
            self.setState({page: <HomePage />});
        });
        router.addRoute('some/:id', function(id) {
            self.setState({page: <SomePage />});
        });
        router.start();
    },
    render: function() {
        return this.state.page;
    }
});