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:
"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
var HelloWorld = React.createClass({
render: function(){
return (
<div>
<h1>hello world</h1>
</div>
);
}
});
Rendering a Component
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 statecomponentDidMount
: runs after a component is rendered. After this we have a DOM, good spot to do stuff with the DOM, ajax requests, etccomponentWillReceiveProps
: 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 returnfalse
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:
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:
- Use
Route
to map a url to a route - Use
DeaultRoute
to define the default route of your application (when none is given) - Use
NotFoundRoute
to define a view for a route that is not found (like a 404 but http://leanpub.com/javascriptmancy-mastering-arcane-art-of-writing-awesome-javascript-for-csharp-developers/c/hb2Qh4We4ggMin the client) - Use
Redirect
to redirect to an existing route
Routes and Parameterized Routes
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={{courseId:1}}>Cooking Course</Link>
module.exports = routes;
"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
<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
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
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 viewwillTransitionFrom
executes before leaving the current view
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
- style:
- 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
- style:
By default the react router will use hash-based location, but you can enable the history API based location using Router.HistoryLocation
like this:
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
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;
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:
- Action: A user interacts with a react component view and this triggers an action
- Dispatcher: A dispatcher notifies any stores that have subscribed to this dispatcher about the action
- Store: They hold the application state. When a store changes as a result of an action, the react component is updated.
- 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
andremoveChangeListener
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
- Super great course on react and flux on Pluralsight
Written by Jaime González García , dad, husband, software engineer, ux designer, amateur pixel artist, tinkerer and master of the arcane arts. You can also find him on Twitter jabbering about random stuff.