barbarian meets coding

WebDev, UX & a Pinch of Fantasy

React.js and Flux

This article is part of my personal wiki where I write personal notes while I am learning new technologies. You are welcome to use it for your own learning!

Intro

React is a modern javascript framework that let’s you easily create super interactive user interfaces.

Main Tenets

  • HTML should be a projection of application state and not the source of truth BOOOOM!!
    • Your app is a funtion of props and state
  • JavaScript and HTML belong together, separation of concerns based on domain and not on language.
  • Unidirectional data-flow. No two-way data binding.
    • Easier to reason about state when it only moves in one direction.
    • Two-way data binding can be unpredictable because it results cascades of changes that are hard to debug
  • Inline styles (component-specific styles)

Why Use React

  • Super fast (because it uses a virtual DOM)
  • Component based architecture that is super composable
  • Isomorphic friendly
  • Just a view layer, you can include it easily into existing applications
  • Simple, focused and small API

JSX

JSX is an extension to javascript that allows you to write XML (very similar to HTML) inside your javascript code. It compiles down to JavaScript and looks like this:

1
2
3
4
5
6
7
8
9
10
11
"use strict";
var React = require('react');
var HelloWorld = React.createClass({
  render: function(){
    return (
      <p>hello world</p>
    );
  }
});

module.exports = HelloWorld;

The advantages of this approach is that you can enjoy all the power of javascript when creating templates. Instead of extending HTML like other frameworks do with limited capabilities and proprietary markup, React let’s you reuse all you know about javascript to compose your component’s markup. Additionally, jsx makes the dependencies between your javascript and your HTML more explicit and helps you keep them in sync.

Why do we need a virtual DOM? What do we gain by it?

Operations with the DOM are expensive in terms of performance. A virtual DOM adds an extra layer of abstraction that allows us to update the actual DOM in the most efficient way possible, for instance, by only updating the parts that have changed, or batching changes together. Additionally, having a virtual DOM let’s React do server side rendering and even target native mobile development with React Native.

React Basics

Creating a Component

1
2
3
4
5
6
7
8
9
var HelloWorld = React.createClass({
    render: function(){
        return (
            <div>
                <h1>hello world</h1>
            </div>
            );
    }
});

Rendering a Component

1
2
3
4
5
6
7
8
9
10
11
12
13
var HelloWorld = React.createClass({
    // render component!
    render: function(){
        return (
            <div>
                <h1>hello world</h1>
            </div>
            );
    }
});

// render!
React.render(<HelloWorld/>, document.getElementById('app'));

State in React Components

State in react components is contained in:

  • props:
    • looks like HTML attributes
    • it is immutable
    • they let you pass data down to child components
    • <view name="someView"/>
    • this.props.name
  • State:
    • mutable
    • use only in top level component
    • this.state.count

You can define the initial state of a component in the getInitialState function and default prop values in the getDefaultProps function. These props will be used by a child component when the parent component doesn’t provide any values via attributes.

Component Lifecycle Methods

These methods let you hook up into a component lifecycle:

  • componentWillMount: runs before a component is rendered. This is a good place to set initial state
  • componentDidMount: runs after a component is rendered. After this we have a DOM, good spot to do stuff with the DOM, ajax requests, etc
  • componentWillReceiveProps: called when receiving new props (when props are changed). Not called on initial render.
  • shouldComponentUpdate: called before render when new props or state are being received. Not called on initial render. You can return false from this function to prevent unnecessary renderings. For instance, when a prop/state change doesn’t affect the DOM.
  • componentWillUpdate: runs immediately before rendering when new props or state are being received. Not called on initial render.
  • componentDidUpdate: runs immediately after the component is updated and rerendered.
  • componentWillUnmount: runs immediately after the component is removed from the DOM. Good for cleanup.

Component Composition

React makes it super easy to compose components together. Normally you’ll have a tree structure of components where a parent component owns a series of child component and passes data via props into them.

Controller Views

A Controller View is a react component that has child components. It controls data flows for all its child components via props. Controller views also interact with flux stores. It is recommended to have a single controller view to ensure that data updates only happen once and the render method is only called once.

Prop Validation Through propTypes

propTypes work like code contracts, that is, they let you define which props a particular component is expecting, their types and whether they are required or not. For instance:

1
2
3
4
5
6
propTypes: {
    author: React.PropTypes.object.isRequired,
    validate: React.PropTypes.func.isRequired,
    errors: React.PropTypes.object,
    hasErrors: React.PropTypes.fun.isRequired,
}

For the sake of improved performance, propTypes don’t run in the production, minified version of your web app.

React Router

The de factor standard router to use in React application, React Router is a router library used and maintained by facebook. It lets you define advanced routing schemes for your single page applications in a declarative fashion:

Routes and Parameterized Routes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var React = require('react');
var Router = require('react-router');
var DefaultRoute = Router.DefaultRoute;
var Route = Router.Route;

var App = require('./components/app');
var Home = require('./components/homePage');
var Authors = require('./components/authors/authorPage');
var About = require('./components/about/aboutPage');

var routes = (
    <Route name="app" path="/" handler={App}>
        <DefaultRoute handler={Home}></DefaultRoute>
        <Route name="authors" handler={Authors}></Route>
        <Route name="about" handler={About}></Route>
    </Route>
);

// parameterized queries would work like this
// <route name="courses" path="/course/:courseId" handler={Course} />
// you can link to it by using link
// <Link to="courses" params=>Cooking Course</Link>

module.exports = routes;
1
2
3
4
5
6
7
8
9
"use strict";

var React = require('react');
var Router = require('react-router');
var routes = require('./routes');

Router.run(routes, function(Handler){
    React.render(<Handler/>, document.getElementById('app'));
});

Links

1
2
3
4
5
6
7
<nav>
    <ul>
        <li><Link to="app">Home</Link></li>
        <li><Link to="authors">Authors</Link></li>
        <li><Link to="about">About</Link></li>
    </ul>
</nav>

NotFoundRoute

1
2
3
4
5
6
7
8
9
10
var NotFound = require('./components/notFoundPage');

var routes = (
    <Route name="app" path="/" handler={App}>
        <DefaultRoute handler={Home}></DefaultRoute>
        <Route name="authors" handler={Authors}></Route>
        <Route name="about" handler={About}></Route>
        <NotFoundRoute handler={NotFound} />
    </Route>
);

Redirects

1
2
3
4
5
6
7
8
9
10
11
var routes = (
    <Route name="app" path="/" handler={App}>
        <DefaultRoute handler={Home}></DefaultRoute>
        <Route name="authors" handler={Authors}></Route>
        <Route name="about" handler={About}></Route>
        <NotFoundRoute handler={NotFound} />
        <Redirect from="about-us" to="about" />  // changed route
        <Redirect from="authurs" to="authors" /> // typos
        <Redirect from="about/*" to="about" />   // catch-all
    </Route>
);

Transitions Between Routes

The willTransitionTo and willTransitionFrom methods allow you to execute code between changing routes. You can, for instance, have some authorization mechanism that checks whether or not a given user is authorized to view a route or prompt the user to save a form before leaving a particular view of your application.

  • willTransitionTo executes before loading a new view
  • willTransitionFrom executes before leaving the current view
1
2
3
4
5
6
7
8
9
10
var SomePage = React.createClass({
  statics: {
    willTransitionTo: function(transition, params, query, callback){
      if (!user.isAdmin){
        transition.abort();
        callback();
      }
    }
  }
});

Hash-based Location vs History Location

  • Hash-based
    • style: www.myurl.com#/authors
    • works in all browsers
    • ugly urls
    • not compatible with server rendering
  • History API based
    • style: www.myurl.com/authors
    • based on new HTML5 location APIs (pushState, replaceState, etc)
    • doesn’t work in some legacy browsers
    • nice urls
    • works with server rendering

By default the react router will use hash-based location, but you can enable the history API based location using Router.HistoryLocation like this:

1
2
3
Router.run(routes, Router.HistoryLocation, function(Handler){
    React.render(<Handler/>, document.getElementById('app'));
});

Remember that you need to setup your server so all requests redirect to the main page. For instance, if you are using ASP.NET MVC you’ll want a catch them all rule that redirects to the root controller/action method.

Mixins

Mixins are helpful to encapsulate and reuse pieces of behavior. Every react component has a mixins property where you can specify an array of mixins that will be applied to your component. You can create your own mixins or used some of the mixins provided in React. For instance, the react router provides the React.Navigation mixin to provide programmatic navigation functionality to any component.

Forms

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
var ManageAuthorPage = React.createClass({
    getInitialState: function(){
        return {
            author: {id: '', firstName: '', lastName: ''},
            errors: {},
            dirty: false
        };
    },

    setAuthorState: function(event){
        // update author sate
    },

    saveAuthor: function(event){
        // save author
    },

    authorFormIsValid(){
        // validation
        return true;
    },

    render: function(){
        return (
                <AuthorForm
                  author={this.state.author}
                  onChange={this.setAuthorState}
                  onSave={this.saveAuthor}
                  errors={this.state.errors}
                  />
            );
    }

});

module.exports = ManageAuthorPage;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var AuthorForm = React.createClass({
    propTypes: {
        author: React.PropTypes.object.isRequired,
        onSave: React.PropTypes.func.isRequired,
        onChange: React.PropTypes.func.isRequired,
        errors: React.PropTypes.object
    },
    render: function(){
        return (
            <form>
                <h1>Manage Author</h1>
                <Input name="firstName"
                       label="First Name"
                       value={this.props.author.firstName}
                       onChange={this.props.onChange}
                       error={this.props.errors.firstName}
                       />

                <Input name="lastName"
                       label="Last Name"
                       value={this.props.author.lastName}
                       onChange={this.props.onChange}
                       error={this.props.errors.lastName}
                       />

                <input type="submit" value="Save"
                       className="btn btn-default"
                       onClick={this.props.onSave}
                       />
            </form>
            );
    }

});

Flux

Flux comes as a response to the common MVC architecture and two way data binding which as it grows in complexity becomes harder and harder to reason about and debug. The core proposition of flux is to use a uni-directional dataflow that is much easier to reason about (data only goes in one direction) and follows these steps:

  1. Action: A user interacts with a react component view and this triggers an action
  2. Dispatcher: A dispatcher notifies any stores that have subscribed to this dispatcher about the action
  3. Store: They hold the application state. When a store changes as a result of an action, the react component is updated.
  4. View: When the user interacts with the newly rendered View a new action occurs and we’re back at 1.

It is an unidirectional data flow because data always follows the same direction, from the action to the view, so that the view itself can never update the state of a react component directly. This results in a more strict application model where all changes are triggered by actions and therefore changes becomes easier to reason about, trace and debug.

Actions

They are triggered by a user interacting with our application and are represented via events. They can also be triggered by the server as a response to some earlier user interaction (errors, etc).

Actions are similar to a command in the command design pattern and have a type and payload (data).

Dispatcher

The dispatcher (which is unique) receives actions and dispatches them to the different stores that are interested in a given action. All data in the application flows through the dispatcher and is routed to the stores. It basically holds a list of callbacks from the interested stores and uses them to broadcast the actions to them.

Stores

Stores hold application state, logic and data retrieval. It is not a model, it contains a model. They register themselves to be notified of the actions that interest them.

They use Node’s EventEmitter to interact with the outside world through events. Using events they can notify react components about the application state having changed:

  • They extend EventEmitter
  • They implement a addChangeListener and removeChangeListener to add/remove component subscriptions to events
  • They call emitChange to notify subscribed components

Controller Views

Controller views are the top level component that manage all data flow for their children. They usually have control over the state of their child components which they share through props and contain the logic. Additionally, the encapsulate interaction with stores.

References

Comments