Workshop React

n'friends

Sommaire

  • What is React ?
  • Immutability
  • Flux
  • Cursors
  • Global State

ReactJS

Removing UI Complexity

It's like the V of MVC.

It's more like directives part of Angular (don't say it out loud)

What for ?

  • Building large applications with data that changes over time.
  • DOM manipulation is an expensive operation and should be minimized.
  • Build re-usable components.

 

DO NOT MIX LOGIC AND PRESENTATION

DO NOT WRITE INLINE javascript / CSS

:'<

 

DO NOT MIX LOGIC AND PRESENTATION

 

DO NOT WRITE INLINE JAVASCRIPT / CSS

  • Code is clear
  • App is predictable
  • Reusable components
  • Manage all UI updates
  • Virtual DOM !
  • Separation of concerns
  • Integrate it with everything
  • Isomorphic
  • Agile libs vs Monolethic frameworks concepts

The good

  • JSX Mixing logic with Markup feels a bit dirty
  • ONLY the view, on it's own It's useless for the real world
  • Started from the bottom now we're here

The bad

React has no…

… controllers

… directives

… templates

… global event listeners

… models

… no view models

 

Just Components

//HTML
<header>  
   <div class="name"></div>
</header>  

//JAVASCRIPT
$.post('/login', credentials, function( user ) {
    $('header .name').show().text( user.name );
});
//JAVASCRIPT
class Header extends React.Component{
  constructor(){
    this.state = {name: null};
  }

  componentDidMount(){
    $.post('/login', credentials, function( user ) {
        this.setState({name: user.name});
    });
  }

  render() {  
    return <header>
             { this.state.name ? <div>this.state.name</div> : null }
           </header>;
  }

}

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

No framework + jQuery

React

//JAVASCRIPT
var MyComponent = React.createClass({

        propTypes: { name: React.PropTypes.string.isRequired },
        mixins : [],

        getInitialState: function() { return {count: 1} },
        getDefaultProps: function() { return {name: "Albert Pilon"}  },

        componentWillMount : function() { 
            setInterval(function(){ 
                this.setState({count : this.state.count + 1}) 
            }, 1000)
        },
        componentWillReceiveProps: function() {},
        componentWillUnmount : function() {},

        _trigram : function() {
            return trucpourfaireuntrigram(this.props.name);
        },
        _secondFunction : function() {},

        render: function() {
          return <h1>
            {this.props.name} ({this._trigram}) a {this.state.count} bonbon{this.state.count > 1 && "s"}
          </h1>;
	}

    })

React.render(<MyComponent />, document.body);
React.render(<MyComponent name="Stéphane Montlouis-Calixte" />, document.body);

Hello word

var CommentBox = React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      /* .. */
    });
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    );
  }
});

React.render(
  <CommentBox url="comments.json" pollInterval={2000} />,
  document.getElementById('content')
);

Let's create a comment list

var CommentBox = /* ... */

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

var Comment = React.createClass({
  render: function() {
    var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={{__html: rawMarkup}} />
      </div>
    );
  }
});

React.render(
  <CommentBox />,
  document.getElementById('content')
);

Let's create a comment list - part 2

  • Stateful -> Stateless components | Avoid state

  • Centralize state

  • State should not be set from props

  • Always set propTypes for validation and self-documentation

  • Multi-line JSX

  • Conditional JSX

  • Use the purerender mixin

Some best practices

PureRenderMixin

var ReactComponentWithPureRenderMixin = {
  shouldComponentUpdate: function(nextProps, nextState) {
    return !shallowEqual(this.props, nextProps) ||
           !shallowEqual(this.state, nextState);
  }
};

This only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences. Only mix into components which have simple props and state, or use forceUpdate() when you know deep data structures have changed.

var a = { foo: 'bar' };  
var b = a;

a === b; // true 

var a = { foo: 'bar' };  
var b = { foo: 'bar' };

a === b; // false 

var a = { foo: 'bar' };  
var b = a;  
b.foo = 'baz';  
a === b; // true  

//WTF ?! >:(

Immutable to the rescue !

Immutability

Don't even try to change who I am

Immutability: The true constant is change. Mutation hides change. Hidden change manifests chaos. Therefore, the wise embrace history. - Michael Jordan

 

 

When the past is not preserved, it will come back to haunt you, manifesting as bugs in the program.

Primitives are immutable,

Objects are mutable

    Primitives

  • undefined
  • null
  • boolean - Boolean*
  • string - String*
  • number - Number*

 

*wrapper type

//IMMUTABLE

var me = new String("Angus");
me.length = 2; //(error in strict mode)
me.length; //5 (not 2)
me.valueOf(); "Angus"

var s = "hello";   // Start with some lowercase text
s.toUpperCase();   // Returns "HELLO", but doesn't alter s
s                  // => "hello": the original string has not changed



//MUTABLE

var o = { y:1 };     // Start with an object
o.y = 2;             // Mutate it by changing the value of a property ...

x=[1,2,3]; 
x.reverse(); 
alert(x[0]); // 3
var obj = {
  prop: function (){},
  toto: "truc"
}

obj.toto = "machin";
obj.bidule = "woof";

var immObj = Object.freeze(obj);

// Maintenant que l'objet est gelé, les changements échoueront
obj.toto = "eheh"; // échoue silencieusement
obj.roxor = "ga bu zo meu"; // échoue silencieusement et n'ajoute pas la propriété

Fake Immutability with Object.Freeze()

var obj1 = Immutable.Map({
	foo: "salut",
	bar: "bonjour",
	baz: "zeze"
});
var obj2 = map1.set('foo', "bonjour");

obj1.get('foo'); // salut
obj2.get('foo'); // bonjour


//Array methods return a new immutable Object
var list1 = Immutable.List.of(1, 2);
var list2 = list1.push(3, 4, 5);
var list3 = list2.unshift(0);
var list4 = list1.concat(list2, list3);

Immutable.js

WTF

What the flux ?

Flux

For application that deals with dynamic data

"MVC Does Not Scale, Use Flux Instead" - Abraham Lincoln

  • Actions 
  • Dispatcher
  • Stores
  • Controller Views
var EventDispatcher = {
  _prefix: 'on_',
  listeners: {},
  register: function(evt_name, bind, callback) {
    var _evt_name = this._prefix + evt_name;
    if(typeof this.listeners[_evt_name] == 'undefined') {
      this.listeners[_evt_name] = [];
    }
    this.listeners[_evt_name].push([bind === null ? this : bind, callback]);
  },
  emit: function(evt_name, params) {
    var _evt_name = this._prefix + evt_name;
      if(typeof this.listeners[_evt_name] != 'undefined') {
        for(var i = 0, l = this.listeners[_evt_name].length; i < l; i++) {
          this.listeners[_evt_name][i][1].call(this.listeners[_evt_name][i][0], evt_name, params);
        }
      }
  }
}


EventDispatcher.register('myevent', object1.dosomething);
// inside an object2 method
EventDispatcher.emit('myevent', { custom: 'parameter' });

1. Your Views "Dispatch" "Actions"

//Dispatcher.js
var AppDispatcher = new Dispatcher();  

//Button.react.js
<button onClick={ this.createNewItem }>New Item</button>  

createNewItem: function( evt ) {
    ListActions.add( item );
}


//ListActions.js
ListActions = {

    add: function( item ) {
        AppDispatcher.dispatch({
            eventName: 'new-item',
            newItem: item
        });
    }

};

2. Your "Store" Responds to Dispatched Events

// Global object representing list data and logic
var ListStore = {

    // Actual collection of model data
    items: [],

    // Accessor method we'll use later
    getAll: function() {
        return this.items;
    }

    switch( payload.eventName ) {

        case 'new-item':

            // We get to mutate data!
            ListStore.items.push( payload.newItem );

            // Tell the world we changed!
            ListStore.trigger( 'change' );

            break;
    }

    return true; // Needed for Flux promise resolution

};

3. And emits an change

4. Your View Responds to the "Change" Event

//React component

componentDidMount: function() {  
    ListStore.bind( 'change', this.listChanged );
},

listChanged: function() {  
    // Since the list changed, trigger a new render.
    this.setState({
        list: ListStore.getAll()
    });
},

setState executes render() method, and you're done

Global State

architectural and performance benefits

"A list expressed over time is a stream." - Franck sinatra

om sweet om

  • Clojure Script Interface to React
  • Because immutability
  • Global optimization
  • Single, Normalized Application State

  • Push mutation to the edge of the system
  • If you understand the state, your understand the app
//Whole motherfucking app
myFuckingApp = {
  app: {
     menu_open: true
  },
  auth: {
    data: null,
    form: null
  },
  i18n: {
    formats: {},
    locales: initialLocale,
    messages: messages[initialLocale]
  },
  pendingActions: {},
  todos: {
    editables: {},
    newTodo: {
      title: ''
    },
    // Initial state can contain prefetched lists and maps. List for array, map
    // for object. We can also use sortedByTitle list, if we need sorted data.
    list: [
        {id: 1, title: 'consider ‘stop doing’ app'},
        {id: 1, title: 'consider ‘stop doing’ app'},
        {id: 1, title: 'consider ‘stop doing’ app'},
        {id: 1, title: 'consider ‘stop doing’ app'},
        {id: 1, title: 'consider ‘stop doing’ app'}
    ]
  },
  user: {
    isLoggedIn: false,
    data: null
  }
};

Great, so ?

<Router state="myFuckingApp">
   <App app="this.props.state">
      <Container container="this.props.app">
	 <Header header="this.props.container.header">
	    <Menu menu="this.props.header.menu">
		{this.props.menu.is_open ? <Menu_open> : <Menu_closed> }

LOL, NOPE,

MEET CURSORS !

CUrsors

  • Selectors into the app state which are passed in to components
  • Updates (transact!) are reflected in the global atom
  • Know nothing of the global state or their place in it
  • Component knows to re-render when data in its cursor is changed

 

Let's do this w/react-cursor

var App = React.createClass({
    getInitialState: function () {
        return {
            "a": 10,
            "b": {
                "foo": {
                    "bar": 42,
                    "baz": ['red', 'green']
                }
            }
        };
    },
    render: function () {
        return <pre>{JSON.stringify(this.state, undefined, 2)}</pre>;
    }
});

var Cursor = require('path/to/react-cursor').Cursor;
var cursor = Cursor.build(this)

cursor.refine('a').value            //=> 10
cursor.refine('a').set(11);         //=> a set to 11
cursor.refine('b').refine('foo').value      //=> { 'bar': 42, 'baz': ['red', 'green'] }
cursor.refine('b').refine('foo').set({ 'bar': 43, 'baz': ['red', 'green'] })
cursor.refine('b', 'foo', 'baz', 1).set('blue')

kilix-workshop-react

By Stéphane Montlouis-Calixte

kilix-workshop-react

  • 1,430