React
React is a library for building user interface out of individual, composable components.
Components
Components are the unit for composing user interfaces in React and are essentially state machines. They can be viewed as functions that take in props
and state
and render HTML. Components can only render a single root node, so that multiple nodes must be wrapped in a single root node to be returned. The renderComponent
function is used to render a React component into the DOM within the given container, known as mounting in React. The render
method should be pure.
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
React.renderComponent(
<HelloMessage name="John" />,
document.getElementById('container')
);
State can be modified using the setState(data, callback)
function which merges the data
into the component’s state
and re-renders the component, calling the optional callback
when it is finished rendering.
It’s idiomatic to keep as many components as stateless as possible. A common pattern is to create several stateless components under a stateful component which passes state to them via props
so that the stateless sub-components can be rendered in a declarative manner.
The state that may be contained in components should be the most primitive, absolutely required data that may be changed by event handlers to trigger a UI update. The render
method can be used to compute more sophisticated data from this state automatically as required. State therefore shouldn’t consist of computed data, other components (which should be constructed within render
), or duplicate data from props
.
Reconciliation refers to the process by which React updates the DOM with each new render pass. Children are reconciled according to the order in which they’re rendered, so that if a child node is removed, its contents are replaced with its sibling’s contents and the sibling is destroyed. This may cause problems with stateful components, in which case it may be preferable to hide components instead of destroying them.
Components can be given a key that identifies them so that their state is kept with them across render passes by assigning the key
property. Numerical identities should be preceded by a string to avoid being ordered separately from other properties. It’s also possible to assign components to an object where each component is keyed by what will be its key.
Default properties can be defined with the getDefaultProps
method so that if the parent component doesn’t specify them they take on the default value.
var ComponentWithDefaultProps = React.createClass({
getDefaultProps: function() {
return {
value: 'default value'
};
}
})
A React component that extends a basic HTML element can pass on its properties to the extended HTML element with transferPropsTo
.
var Avatar = React.createClass({
render: function() {
return this.transferPropsTo(
<img src={"/avatars/" + this.props.userId + ".png"} userId={null} />
);
}
});
// <Avatar userId={17} width={200} height={200} />
It’s possible to specify validations for properties using React.PropTypes
. For example, React.PropTypes.component.isRequired
can be used to enforce that only one child is passed to a component as children.
var MyComponent = React.createClass({
propTypes: {
children: React.PropTypes.component.isRequired
},
render: function() {
return <div>{this.props.children}</div>;
}
})
Mixins
Mixins can be used to share common functionality between components. If multiple mixins define the same lifecycle method, each one is guaranteed to be called.
var SetIntervalMixin = {
componentWillMount: function() { this.intervals = []; },
setInterval: function() {
this.intervals.push(setInterval.apply(null, arguments));
},
componentWillUnmount: function() {
this.intervals.map(clearInterval);
}
};
var TickTock = React.createClass({
mixins: [SetIntervalMixin],
getInitialState: function() { return {seconds: 0}; },
// call method from mixin
componentDidMount: function() { this.setInterval(this.tick, 1000); },
tick: function() { this.setState({seconds: this.state.seconds + 1}); },
render: function() { return <p>Running for {this.state.seconds} seconds</p>; }
});
Lifecycle
Components have three main stages in their lifecycle.
- Mounting: insertion into the DOM
- Updating: re-render to determine if DOM should be updated
- Unmounting: removal from the DOM
There are three methods that can be implemented to hook into these stages before and after they occur.
Mounting
The getInitialState
method is invoked before a component is mounted to determine the initial state data. The componentWillMount
method is invoked right before mounting. The componentDidMount
method is invoked right after mounting, and should contain initialization that requires DOM nodes.
Once mounted, composite components support the getDOMNode
method to obtain a reference to the DOM node, as well as the forceUpdate
method to force an update when it’s known that a deeper aspect of the state has changed without using setState
.
Updating
The componentWillReceiveProps
method is called whenever a mounted component receives a new props object (passed through this method), and should be used to compare the existing props with the new props to perform any potential state transitions using setState
.
The shouldComponentUpdate
method is used to determine if any changes to the component warrant an update to the DOM. This should be used to compare the current and new state and props and return false to tell React that it doesn’t need to update. This method receives the new set of props and state.
The componentWillUpdate
method is called right before updating occurs and receives the new set of props and state, while the componentDidUpdate
method is called right after updating completes and receives the old set of props and state.
Unmounting
The componentWillUnmount
method is called right before a component is unmounted and destroyed, so that cleanup may be handled.
Event Handling
Event handlers can be established by using the HTML property in camel-case, such as onClick
. All methods are automatically bound to their component instance.
Event handlers aren’t attached to individual nodes. Instead, React establishes a top-level event listener that catches all events. When components are mounted or unmounted, an internal mapping is updated to reflect this.
DOM
React maintains a fast in-memory DOM representation known as the virtual DOM. The render
method returns a description of the DOM which can then be diff’ed with the virtual DOM to compute the fastest way to update the browser DOM to reflect any changes.
Sometimes it may be necessary to interact directly with the DOM, either to interoperate with third-party libraries or to perform non-reactive changes such as setting focus, which can’t easily be inferred via React’s data flow (props and state).
To interact with the actual browser DOM, a reference to a DOM node reference must be retrieved using the getDOMNode
component method, which only works on mounted components. However, to be able to call this method on the component, it must be possible to get a reference to the component’s backing instance.
Since what’s returned by render
are not the rendered, backing instances and instead are descriptions of the component’s sub-hierarchy, it’s not possible to obtain references to components in the following manner:
render: function() {
var input = <input />;
this.savedRef = input;
return <div>{input}</div>;
}
Instead, components can be referenced by giving them a ref
property which makes the component’s backing instance accessible via this.refs
. Once a reference to the component is attained, a DOM node reference can be retrieved with the getDOMNode
method.
var MyComponent = React.createClass({
handleClick: function() {
this.refs.textInput.getDOMNode().focus();
},
render: function() {
return (
<div>
<input type="text" ref="textInput" />
<input type="button" value="click this" onClick={this.handleClick} />
</div>
);
}
})
As a disclaimer, never access refs within any component’s render
method or while any component’s render
method is running anywhere in the call stack.
Forms
Form components differ from native components because they can be mutated by user interactions. Specifically, they support props that are affected by user actions.
Prop | Components |
---|---|
value |
input and textarea |
checked |
checkbox and radio |
selected |
option |
Although in HTML the value of textarea
is set via children, it should be set via value
in React. The onChange
prop can be listened to for when these properties change in response to user interactions.
If an input
component has a value
property set, then it is considered a controlled component, so that the rendered component always reflects the value
property; user input has no effect. In order for a controlled component to reflect user input, it should explicitly set the value in response to the change.
getInitialState: function() { return {value: 'Hello!'} },
handleChange: function(e) { this.setState({value: e.target.value}) },
render: function() {
var value = this.state.value;
return <input type="text" value={value} onChange={this.handleChange} />;
}
If an input
component doesn’t supply a value
property, then it’s uncontrolled and so the rendered element reflects user input. A default value can be provided without making the component controlled by setting the defaultValue
prop. There’s also a defaultChecked
that can be used with checkboxes.
render: function() {
return <input type="text" defaultValue="Hello!" />;
}
JSX
JSX is an optional HTML-like syntax that can be used for function calls that generate markup. A special comment header pragma is required to denote that the file should be processed by the JSX transformer.
/** @jsx React.DOM */
JSX can be used to construct instances of React DOM components and composite components created with createClass
. It’s important to realize that JSX has no notion of the DOM, and instead transforms elements into function calls.
var app = <Nav color="blue"><Profile>click</Profile></Nav>;
var app = Nav({color: "blue"}, Profile(null, "click"));
var MyComponent = React.createClass({/* ... */});
var app = <MyComponent someProperty={true} />;
JavaScript expressions can be embedded into JSX with curly braces {}
within attributes and as children. Comments should also be contained within expressions.
var person = <Person name={window.isLoggedIn ? window.name : ''} />;
var person = Person({name: window.isLoggedIn ? window.name : ''});
var content = <Container>{window.isLoggedIn ? <Nav /> : <Login />}</Container>;
var content = Container(null, window.isLoggedIn ? Nav(null) : Login(null));
Raw HTML can be inserted with a specific API.
<div dangerouslySetInnerHTML={{__html: 'First · Second'}} />
Flux
Flux is an architecture for apps where data flows in a unidirectional cycle. It consists of a dispatcher, stores, and views.
- Views: perform actions—usually due to user interactions—which calls into the dispatcher with a data payload
- Dispatcher: invoke callbacks registered by stores, sending action’s data payload to interested stores
- Stores: respond to actions they’re interested in, receiving data payload, and emit “change” event
- Views: listen to “change” events and re-render accordingly either implicitly or explicitly; back to #1
All data flows through the dispatcher which acts as a central hub. Actions are calls into the dispatcher and usually originate from user interactions with views. The dispatcher then invokes callbacks that the stores registered with it, thereby dispatching the data payloads contained within the actions to all stores. Within the registered callbacks, stores respond to the actions they’re interested in and then emit a “change” event to alert controller-views to the fact that the data layer has been modified. The Controller-views listen to the change events and retrieve data from stores in an event handler, then call their own render
method via setState
or forceUpdate
to update themselves and all of their children.
Dispatcher
The dispatcher is the central hub through which all data flows, acting as a registry of callbacks into the stores, such that each store registers itself and provides a callback. Stores can declaratively wait for other stores to finish updating and then update themselves accordingly.
The dispatcher exposes a method that allows a view to trigger a dispatch to the stores, with a data payload included.
The dispatcher can manage the dependencies between stores using the waitFor
method which specifies a list of dispatcher registry indexes and a final callback to call after the callbacks at the given indexes have completed.
case 'TODO_CREATE':
Dispatcher.waitFor([
PrependedTextStore.dispatcherIndex,
YetAnotherStore.dispatcherIndex
], function() {
TodoStore.create(PrependedTextStore.getText() + ' ' + action.text);
TodoStore.emit('change');
});
break;
Stores
Stores contain the application state and logic, and are similar to models in MVCs. They receive the action’s data payload as a parameter, which contains a type attribute identifying the action’s type, so that a switch statement may be used to interpret the payload and provide proper hooks into the store’s internal methods.
Views and Controller-Views
Near the top of the nested view hierarchy there is a special kind of view that listens for events broadcast by the stores that it depends on. This can be called a controller-view since it provides the glue code to get the data from the stores and pass it down the chain of its descendants.
When the controller-view receives the event from the store, it requests the data it needs via the store’s public getter methods, then calls its own setState
and forceUpdate
to re-render itself and its descendants. It’s common to pass the entire state of the store down the chain of views so that each descendant can use what it needs.