react-primer-draft
A primer for building Single-Page Applications with React.
Status
This is a work in progress.
At the time of writing, the examples were written for React 0.12.x
. This guide will be updated with examples for React 0.13
ES6 classes
soon!
Progress
- Part 1: Intro to React (completed)
- Part 2: Harmony aka ES6 aka ES2015 (in progress)
Table of Contents
- Author's Note
- Part 0: Preface
- Part 1: Intro to React
- 1.0: React Component
- 1.1: React JSX
- 1.2: React Supported Attributes
- 1.3: React Supported Events
- 1.4: React Supported Events Continued
- 1.5: React Props
- 1.6: React State
- 1.7: React Lifecycle Events
- 1.8: React Dynamic Children
- 1.9: React Nested Views
- 1.10: React Mixins
- 1.11: React Pure Render
- 1.12: React and 3rd Party Libraries
- 1.13: React Component Parent and Child Communication
- 1.14: React Fetching Data
- 1.15: React Developer Tools
- Part 2: Harmony aka ES6 aka ES2015
- Part 3: Useful Libraries
- Part 4: Webpack
- Part 5: React Router
- Part 6: Flux
- Part 7: Testing
- Part 8: Rails (Bonus Chapter)
Authors Note
This primer makes use of several libraries, but it is by no means a "React the right way". It's just a introduction to how I am building my own React applications. The goal of this primer to help developers get familiar with React and dive right in. Maybe you will come up with approaches that work better for you and I hope that you share them with the community! Or if you're already well versed, help improve this document so others in the community can benefit. Sharing is caring!
This guide is dedicated to the engineers at Jellyvision, we are hiring so check us out. :D
~ Michael Chau (gh: @mikechau, twtr: @money_mikec)
Preface
What people are saying about React:
My favorite part of React is what I loved about MooTools: to use it effectively you learn JavaScript, not a DSL: useful your whole career.
#reactjs
Haven’t been so excited about programming since learned Rails: http://facebook.github.io/react ! Kudos @floydophone & FB team!
“It wasn’t my intention to use React for everything, but I found myself moving so quickly that I found I’d used it everywhere… had a blast”
Remember to just give it 5 minutes.
React Community
The React community is super friendly!
Connect with other React developers at:
- Reactiflux - Slack for React developers
- #reactjs on irc.freenode.net
- Google Groups
- Stack Overflow
- @reactjs
- #reactjs
React Documentation
The React documentation is very good. Use this primer as a introduction and then read more about React by viewing the official documentation:
There's also a ton of resources at: Awesome React
Intro to React
React is a JavaScript library by Facebook, it describes itself as a javascript library for building user interfaces.
Developers often call it the V in MVC, or talk about the virtual DOM (not to be confused with the shadow DOM). I like React for its declarative style, lifecycle event hooks, and the fact that a React component describes its view at anytime. By breaking down the view into components, writing React starts to become very natural. React has been a pleasure to work with. You no longer need to understand the entire flow of the application at once, you can start at a component and work your way up or down.
This primer is meant to get you rapidly ready to start working with a React application. Its goal is not to teach and explain everything, but merely introduce concepts and help you form the right questions to ask and to have an idea of where to look for an answer. It is OK if you do not understand everything at first, just keep working at it by commiting the code you see to muscle memory and reading up on the documentation. Hopefully through reflection, and incubation, the concepts here will start to make sense.
Don't be afraid of React either. It may seem complex but is quite simple with a very small API of a dozen "essential" methods:
- render
- getInitialState
- getDefaultProps
- propTypes
- mixins
- componentWillMount
- componentDidMount
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
- componentDidUpdate
- componentWillUnmount
React Component
A React component encapsulates everything. It does not separate the view from the view logic, but rather merges the two together. Separating these does not really make sense when building a user interface: the view and its view logic are inevitably tightly coupled. Rather than jumping between a template file and some sort of view-controller it makes sense to keep them together. React components are usually small enough that this is not a big deal to have the two together, and if it does get to be too large you can break down your component into smaller components.
A key point from the React documentation:
Components are Just State Machines
React thinks of UIs as simple state machines. By thinking of a UI as being in various states and rendering those states, it's easy to keep your UI consistent.
In React, you simply update a component's state, and then render a new UI based on this new state. React takes care of updating the DOM for you in the most efficient way.
Read more: Interactivity and Dynamic UIs
React JSX
JSX is pretty interesting. It basically allows using an HTML/XML-like syntax within JavaScript-based React components. Of course this wouldn't work if you tried to do that then serve it. Choosing to write your React component in JSX requires a transform process. This is typically handled through a build process or tool, like webpack.
var Button = React.createClass({
render: function() {
return (
<a className="btn btn-default">I am a button! Click me!</a>
);
}
});
React.render(<Button />, document.getElementById('content'));
Here you can see a React component has a render
method, which returns markup. It's easy to see the shape of the output at a glance.
NOTE: As of version 0.13 React components can only ever return a single "root" element or sub-component.
For example, this is forbidden:
return (
<div>Test</div>
<div>Test 2</div>
);
It has to be:
return (
<div>
<div>Test</div>
<div>Test 2</div>
</div>
);
To render a React component in the body all you need to do is:
React.render(<Button />, document.getElementById('content'));
Simply call the render method, pass in the component, and the DOM node you want to render to.
NOTE: You should avoid rendering your React component directly into document.body
, as it can lead to subtle bugs.
When rendering a React component inside a DOM node, React wants complete ownership of the node. You should not add children to or remove children from a node in which React inserted a component.
So just create a <div id="content"></div>
and then use document.getElementById('content')
.
Like so:
<!-- index.html -->
<body>
<div id="content"></div>
</body>
// App.jsx
React.render(<App />, document.getElementById('content'));
Read more: JSX in Depth
React Supported Attributes
React works with most common HTML elements, for example:
var Link = React.createClass({
render: function() {
return (
<a href="http://google.com">Google</a>
);
}
});
React.render(<Link />, document.getElementById('content'));
You just made an a
tag! It can now be called via <Link />
.
Read more: React Tags and Attributes
React Supported Events
React has a cross-compatibility layer for most specific browser events. Going back to our Link
:
var Link = React.createClass({
render: function() {
return (
<a href="http://google.com" onClick= {this.handleClick}>Google</a>
);
},
handleClick: function(e) {
e.preventDefault();
alert('You clicked me!');
}
});
React.render(<Link />, document.getElementById('content'));
Now, I know what you're thinking. Inline events, isn't that bad? It looks like it's inline but it's really not. React will use event delegation behind the scenes. So now we have a very declarative way to associate events to DOM elements. Now there's no confusion as to what elements have what events and there is no hassle of managing binding hooks between DOM and javascript (e.g. ids or classes).
Here's a key point from the React documentation:
Event Handling and Synthetic Events
With React you simply pass your event handler as a camelCased prop similar to how you'd do it in normal HTML. React ensures that all events behave identically in IE8 and above by implementing a synthetic event system. That is, React knows how to bubble and capture events according to the spec, and the events passed to your event handler are guaranteed to be consistent with the W3C spec, regardless of which browser you're using.
If you'd like to use React on a touch device such as a phone or tablet, simply call
React.initializeTouchEvents(true);
to enable touch event handling.
Under the Hood: Autobinding and Event Delegation
Autobinding: When creating callbacks in JavaScript, you usually need to explicitly bind a method to its instance such that the value of this is correct. With React, every method is automatically bound to its component instance. React caches the bound method such that it's extremely CPU and memory efficient. It's also less typing!
Event delegation: React doesn't actually attach event handlers to the nodes themselves. When React starts up, it starts listening for all events at the top level using a single event listener. When a component is mounted or unmounted, the event handlers are simply added or removed from an internal mapping. When an event occurs, React knows how to dispatch it using this mapping. When there are no event handlers left in the mapping, React's event handlers are simple no-ops. To learn more about why this is fast, see David Walsh's excellent blog post.
In the example, we defined a function, and simply passed the function to the onClick
property.
When the click event occurs, we receive back a synthetic event we can manipulate as usual e.g. call #preventDefault()
to stop the event's default action, or access the event's #target
.
Read more: React Events
React Supported Events Continued
To use the same event handler in multiple bindings and contexts, you may want to attach custom data to DOM nodes as you'd do in native javascript:
var Link = React.createClass({
render: function() {
return (
<div>
<a href="http://google.com" onClick={this.handleClick} data-link="Google">Google</a>
<br />
<a href="http://facebook.com" onClick={this.handleClick} data-link="Facebook">Facebook</a>
</div>
);
},
handleClick: function(e) {
e.preventDefault();
alert('You clicked ' + e.target.getAttribute('data-link'));
}
});
React.render(<Link />, document.getElementById('content'));
That is unnecessary in React, you can use Function#bind
and customize the handler itself via partial application:
var Link = React.createClass({
render: function() {
return (
<div>
<a href="http://google.com" onClick={this.handleClick.bind(null, 'Google')}>Google</a>
<br />
<a href="http://facebook.com" onClick={this.handleClick.bind(null, 'Facebook')}>Facebook</a>
</div>
);
},
handleClick: function(linkName, e) {
e.preventDefault();
alert('You clicked ' + linkName);
}
});
React.render(<Link />, document.getElementById('content'));
According to Javascript Is Sexy:
Partial function application is the use of a function (that accept one or more arguments) that returns a new function with some of the arguments already set. The function that is returned has access to the stored arguments and variables of the outer function. This sounds way more complex than it actually is, so let’s code. ...
An explanation of .bind(this, ...)
vs .bind(null, ...)
by Morhaus
on #reactjs
:
React autobinds methods in the object you pass to
React.createClass()
to the component instance, so usingthis.handleClick.bind(null, 'test')
will ensure that behavior is not messed with...
pass null instead, unless you're not using
React.createClass()
butclass extends React.Component
, in which case methods are not auto bound
this
can be passed to preserve the current context, or we can pass null
if it is not necessary. In this case, since we are using React.createClass
, React will autobind the method for us so we can just pass null
and whatever arguments we want applied.
Read more: Currying
Read more: Understand Javascript's "this" with Clarity and Master It
Read more: React Autobinding
Read more: bind(): React component methods may only be bound to the component instance
Read more: Partial application in JavaScript with bind
React Props
Props are immutable parameters passed by a parent component to a child sub-component. A component can not alter its #props
object (and should not alter the props themselves), the only way for props to change is for a new render to be triggered, where the parent component passes new props to the child.
Props flow downwards, and in JSX they are provided as attributes of the component node:
// Parent Component
var LikeList = React.createClass({
render: function() {
return (
<ul>
<LikeListItem text='turtles.' />
</ul>
);
}
});
// Child Component
var LikeListItem = React.createClass({
render: function() {
return (
<li>
{this.props.text}
</li>
);
}
});
React.render(<LikeList />, document.getElementById('content'));
Here we have the Parent Component, LikeList
, render an unordered list, with one child, a LikeListItem
. To the LikeListItem
we pass a text
property.
A component can access its props through this.props
, text
would be accessed via this.props.text
. Here, the text
prop is simply accessed during rendering.
Read more: Transferring Props
getDefaultProps
getDefaultProps
will be called to get a default set of props, which will be overridden by the props eventually provided by a parent component. This is useful for optional props which have a sensible default value.
// Parent Component
var LikeList = React.createClass({
render: function() {
return (
<ul>
<LikeListItem />
</ul>
);
}
});
// Child Component
var LikeListItem = React.createClass({
getDefaultProps: function() {
return {
text: 'N/A'
};
},
render: function() {
return (
<li>
{this.props.text}
</li>
);
}
});
React.render(<LikeList />, document.getElementById('content'));
If no text
prop is passed, we give it a default value of N/A
. If defined, getDefaultProps
must return an object.
Read more: Default Prop Values
propTypes
propTypes
documents the props expected by a component and defines validators for generating warnings in the dev console.
It is recommended that you define propTypes
in any component that expects props. It is useful not only for validation during development but can be a way of documenting the component. It also makes it clear to others what the component expects.
A key point from the React documentation
:
Prop Validation
As your app grows it's helpful to ensure that your components are used correctly. We do this by allowing you to specify propTypes. React.PropTypes exports a range of validators that can be used to make sure the data you receive is valid. When an invalid value is provided for a prop, a warning will be shown in the JavaScript console. Note that for performance reasons propTypes is only checked in development mode. Here is an example documenting the different validators provided: ...
Example:
// Child Component
var LikeListItem = React.createClass({
propTypes: {
text: React.PropTypes.string
},
getDefaultProps: function() {
return {
text: 'N/A'
};
},
render: function() {
return (
<li>{this.props.text}</li>
);
}
});
Here we declare that the text
property must be a string
. By default, props are optional and should have a default value (provided through getDefaultProps
).
It is also possible to mandate that a prop be provided with the isRequired
validator:
// Child Component
var LikeListItem = React.createClass({
propTypes: {
text: React.PropTypes.string.isRequired
},
...
getDefaultProps
was removed: since the property is required, there is no reason to provide a default value for it.
Read more: Prop Validation
refs
React documentation introduction:
React supports a very special property that you can attach to any component that is output from render(). This special property allows you to refer to the corresponding backing instance of anything returned from render(). It is always guaranteed to be the proper instance, at any point in time.
Here is an example of how you can treat a ref
like an id
:
// Parent Component
var LikeList = React.createClass({
componentDidMount: function() {
console.log(this.refs.first.getDOMNode());
},
render: function() {
return (
<ul>
<LikeListItem ref="first" text='turtles.' />
</ul>
);
}
});
// Child Component
var LikeListItem = React.createClass({
render: function() {
return (
<li>
{this.props.text}
</li>
);
}
});
React.render(<LikeList />, document.getElementById('content'));
NOTE: In React 0.13, Component#getDOMNode()
should be replaced by React.findDOMNode(Component)
, the first one generates a warning in the console.
In this example, we can access the ref
of first
via this.refs.first
. After componentDidMount
has been called, the console output will be:
<li data-reactid=".0.0">turtles.</li>
Read more: More About Refs
children
There may be situations where you want components to wrap provided components rather than generate those from props:
var Likes = React.createClass({
render: function() {
return (
<div>
<LikesListWrapper>
<LikeListItem text="turtles" />
</LikesListWrapper>
</div>
);
}
});
// Parent Component
var LikesListWrapper = React.createClass({
render: function() {
return (
<ul>
{ this.props.children }
</ul>
);
}
});
// Child Component
var LikeListItem = React.createClass({
render: function() {
return (
<li>
{this.props.text}
</li>
);
}
});
React.render(<Likes />, document.getElementById('content'));
Children are placed between a component's opening and ending tags, like regular HTML elements. The wrapper component can access those children via this.props.children
.
Read more: Children in JSX
className
Because class
is a reserved JavaScript keyword, to set the class
of an element you will need to use the className
property name instead.
render: function() {
return (
<a className="btn btn-default">I am a button with a class!</a>
);
}
Review: Tags and Attributes for more details on the supported HTML
tags and attributes
.
Review: Events for the supported browser events.
Passing a Prop
When providing a boolean prop in JSX, passing the value is not necessary, as in HTML you can just add the key:
// SomeComponent.jsx
var SomeComponent = React.createClass({
render: function() {
return (
<AnotherComponent checked />
);
}
});
AnotherComponent
's this.props.checked
would resolve to true
.
React State
As Pete Hunt noted, shared mutable state is the root of evil. Yet (mutable) state is often necessary. To that end, React components provide mutable state but not shared mutable state: only a component can alter its state, and a component can only alter its own state.
On a component's state change, a re-render of the tree will be automatically triggered.
State is useful for intermediate or self-contained component data, which should not or needs not be published externally. For instance a choice between liking something or not:
<!-- index.html -->
<body>
<p>Do you like fish sticks?</p>
<p>Response: I <span id="response">______</span> fishsticks.</p>
<a id="like" class="btn btn-success">I like them.</a>
<a id="dislike" class="btn btn-danger">I dislike them.</a>
</body>
// logic.js
$('#like').on('click', function(e) {
e.preventDefault();
$('#response').text('like');
});
$('#dislike').on('click', function(e) {
e.preventDefault();
$('#response').text('dislike');
});
The state of having liked or disliked fish sticks is fully internal, it does not come from an external source (a parent component) and is not published anywhere, thus in React:
var LikeComponent = React.createClass({
getInitialState: function() {
return {
response: ''
};
},
render: function() {
return (
<div>
Do you like fish sticks?
<br /><br />
Response: I {this.state.response || '_____'} fishsticks.
<br /><br />
<a className="btn btn-success" onClick={this.handleLike}>I like it.</a>
<a className="btn btn-danger" onClick={this.handleDislike}>I dislike it.</a>
</div>
);
},
handleLike: function(e) {
e.preventDefault();
this.setState({
response: 'like'
});
},
handleDislike: function(e) {
e.preventDefault();
this.setState({
response: 'dislike'
});
}
});
React.render(<LikeComponent />, document.getElementById('content'));
getInitialState
has the same purpose as getDefaultProps
, but because state is not shared rather than providing a default it provides the complete internal state of the component. State items can then be accessed through this.state
, much like props via this.props
.
State must not be altered directly.
It should generally be altered via setState
, which merges the provided object into the existing state then triggers a re-render: if the state is {foo: 1, bar: 2}
, this.setState({foo: 2})
will result in a new state of {foo: 2, bar: 2}
.
NOTE: although sometimes tempting, setting up the initial state from props is generally an anti-pattern, it's usually better to compute from props on the fly.
For example:
getInitialState: function() {
return {
count: this.props.initialCount // this is fine because we make it clear that it is an initial value
}
}
Key Point:
Props in getInitialState Is an Anti-Pattern
Using props, passed down from parent, to generate state in getInitialState often leads to duplication of "source of truth", i.e. where the real data is. Whenever possible, compute values on-the-fly to ensure that they don't get out of sync later on and cause maintenance trouble.
Read more: Props in getInitialState Is an Anti-Pattern
Read more: State
React Lifecycle Events
React components have a somewhat involved lifecycle, but React provides a number of events/methods to hook in and act at various lifecycle points. The React documentation covers this section very well, so we will just quote it for convenience.
Read more: Component Specs and Lifecycle Events
Mounting: componentWillMount
componentWillMount()
Invoked once, both on the client and server, immediately before the initial rendering occurs. If you call
setState
within this method,render()
will see the updated state and will be executed only once despite the state change.
Read more: componentWillMount
Mounting: componentDidMount
componentDidMount()
Invoked once, only on the client (not on the server), immediately after the initial rendering occurs. At this point in the lifecycle, the component has a DOM representation which you can access via
React.findDOMNode(this) [React 0.13+]
orthis.getDOMNode() [React 0.12.x]
.If you want to integrate with other JavaScript frameworks, set timers using setTimeout or setInterval, or send AJAX requests, perform those operations in this method.
Read more: componentDidMount
Updating: componentWillReceiveProps
componentWillReceiveProps(object nextProps)
Invoked when a component is receiving new props. This method is not called for the initial render.
Use this as an opportunity to react to a prop transition before
render()
is called by updating the state usingthis.setState()
. The old props can be accessed viathis.props
. Callingthis.setState()
within this function will not trigger an additional render.NOTE: There is no analogous method
componentWillReceiveState
. An incoming prop transition may cause a state change, but the opposite is not true. If you need to perform operations in response to a state change, usecomponentWillUpdate
.
Read more: componentWillReceiveProps
Updating: shouldComponentUpdate
boolean shouldComponentUpdate(object nextProps, object nextState)
Invoked before rendering when new props or state are being received. This method is not called for the initial render or when
forceUpdate
is used.Use this as an opportunity to
return false
when you're certain that the transition to the new props and state will not require a component update.If
shouldComponentUpdate
returns
false
, thenrender()
will be completely skipped until the next state change. (In addition,componentWillUpdate
andcomponentDidUpdate
will not be called.)By default,
shouldComponentUpdate
alwaysreturns
true
to prevent subtle bugs when state is mutated in place, but if you are careful to always treat state as immutable and to read only from props and state inrender()
then you can overrideshouldComponentUpdate
with an implementation that compares the old props and state to their replacements.If performance is a bottleneck, especially with dozens or hundreds of components, use
shouldComponentUpdate
to speed up your app.
Updating: componentWillUpdate
componentWillUpdate(object nextProps, object nextState)
Invoked immediately before rendering when new props or state are being received. This method is not called for the initial render.
Use this as an opportunity to perform preparation before an update occurs.
Note: You cannot use
this.setState()
in this method. If you need to update state in response to a prop change, usecomponentWillReceiveProps
instead.
Read more: componentWillUpdate
Updating: componentDidUpdate
componentDidUpdate(object prevProps, object prevState)
Invoked immediately after the component's updates are flushed to the DOM. This method is not called for the initial render.
Use this as an opportunity to operate on the DOM when the component has been updated.
Read more: componentDidUpdate
Unmounting: componentWillUnmount
componentWillUnmount()
Invoked immediately before a component is unmounted from the DOM.
Perform any necessary cleanup in this method, such as invalidating timers or cleaning up any DOM elements that were created in componentDidMount.
Read more: componentWillUnmount
React Dynamic Children
React is great for rendering dynamic children. It's never been so easy.
Let's take the example:
var animalsListData = [
{ id: 1, animal: 'tiger', name: 'Vee' },
{ id: 2, animal: 'lion', name: 'Simba' },
{ id: 3, animal: 'dog', name: 'Buck' },
{ id: 4, animal: 'sealion', name: 'Seel' }
];
var AnimalsList = React.createClass({
getInitialState: function() {
return {
animals: []
};
},
render: function() {
if (!this.state.animals.length) {
return (
<div>No animals!</div>
);
}
return (
<ul>
{
this.state.animals.map(function(animal, index) {
return (
<li key={index}>
{animal.name} the { animal.animal }!
</li>
);
})
}
</ul>
);
}
});
React.render(<AnimalsList />, document.getElementById('content'));
Because this.state.animals
is initially empty, the first render will display No animals!, as coded in the first part of the render
method. We only render the list if there are animals in the list.
var animalsListData = [
{ id: 1, animal: 'tiger', name: 'Vee' },
{ id: 2, animal: 'lion', name: 'Simba' },
{ id: 3, animal: 'dog', name: 'Buck' },
{ id: 4, animal: 'sealion', name: 'Seel' }
];
var AnimalsList = React.createClass({
getInitialState: function() {
return {
animals: []
};
},
componentDidMount: function() {
this._fetchRemoteData();
},
render: function() {
if (!this.state.animals.length) {
return (
<div>
No animals!
<br />
<a
href="#fetch"
className="btn btn-primary"
onClick={this.handleFetchClick}
>
Fetch
</a>
</div>
);
}
return (
<div>
<ul>
{
this.state.animals.map(function(animal, index) {
return (
<li key={index}>
{animal.name} the {animal.animal}!
</li>
);
})
}
</ul>
<a
href="#reset"
className="btn btn-danger"
onClick={this.handleResetClick}
>
Reset
</a>
</div>
);
},
handleResetClick: function(e) {
e.preventDefault();
this.setState({
animals: []
});
},
handleFetchClick: function(e) {
e.preventDefault();
this._fetchRemoteData();
},
_fetchRemoteData: function() {
setTimeout(function() {
this.setState({
animals: animalsListData
});
}.bind(this), 2000);
}
});
React.render(<AnimalsList />, document.getElementById('content'));
For the purpose of this example, we'll emulate data fetching with a setTimeout
call. In a real-world situation, you would use whatever AJAX library you prefer.
As soon as the component mounts, we "fetch" the remote data, which will update the internal state and re-render the component 2 seconds later. The fetching is done inline but could be split into a separate method (e.g. _fetchRemoteData
), and while we're fetching everything on mount, it could also be fetched only on a user action.
This example is a bit verbose, but hopefully it drives home how you can build your components. Do you see how intertwined your view and view logic end up being? Right from the render
method, you can see exactly what it is going to output and what events are attached to what.
So let's summarize what is happening here:
- The
state
is initialized,this.state.animals
set to an empty array. - The component will mount, we do nothing here.
- When
render
is called, we check if there is anything insidethis.state.animals
, if there is nothing, we render the special case noting the lack of animals. - The component did mount, we call
this._fetchRemoteData()
- When
this._fetchRemoteData()
completes,this.setState(...)
is called and a newrender
happens! The update lifecycle events are also triggered. - The user can use the available buttons to reset or update the list of animals.
To render dynamic children, simply map
over your collection and return the components you want rendered by passing in the collection's item attributes as props.
NOTE: When mapping over an array, the result components must be given a key
.
key
Think of the key
property as a unique identifier for the components being returned from map
.
Key point:
When React reconciles the keyed children, it will ensure that any child with key will be reordered (instead of clobbered) or destroyed (instead of reused).
So let's review:
render: function() {
return (
<ul>
{
this.state.animals.map(function(animal, index) {
return (
<li key={index}>
{animal.name} the {animal.animal}!
</li>
);
})
}
</ul>
);
}
As we map over this.state.animals
, we set the key
prop to the current index in the array.
In general, it is recommended you pass the current index to key
, instead of generating a unique identifier or using one from the object.
funkiee from Hacker News explains why (Marko vs. React: Performance Benchmark):
Not necessarily addressing the speed portion, but if you're going to give a key to a repeated item in React, it is best to use the index instead of a unique identifier(if the overall DOM structure does not change much between renders) so that React does not destroy and recreate each item on tree change. ~ funkiee - Hacker News
As a follow up, you are probably thinking:
It re-creates items even when they have unique identifiers? Is that a bug? ~ myhf - Hacker News
To which, funkiee responds:
It's as intended. If the ID in a list of 100 changes, and that ID is the key, React is going to assume the tree is different during reconciliation. If you were to use an index, like 0, on pagination the key is still 0 and as such the DOM nodes will be reused. http://facebook.github.io/react/docs/reconciliation.html See #2 in the Trade-offs section. ~ funkiee - Hacker News
For your convenience, here are the trade-offs quoted from the React documentation:
Trade-offs
It is important to remember that the reconciliation algorithm is an implementation detail. React could re-render the whole app on every action; the end result would be the same. We are regularly refining the heuristics in order to make common use cases faster.
In the current implementation, you can express the fact that a sub-tree has been moved amongst its siblings, but you cannot tell that it has moved somewhere else. The algorithm will re-render that full sub-tree.
Because we rely on two heuristics, if the assumptions behind them are not met, performance will suffer.
- The algorithm will not try to match sub-trees of different components classes. If you see yourself alternating between two components classes with very similar output, you may want to make it the same class. In practice, we haven't found this to be an issue.
- If you don't provide stable keys (by using Math.random() for example), all the sub-trees are going to be re-rendered every single time. By giving the users the choice to choose the key, they have the ability to shoot themselves in the foot.
This will probably make more sense after reviewing the Reconciliation section in the React documentation.
NOTE: You only need to set the key
for the parent container component, it does not matter if it is a custom React component or an HTML container.
Example:
// CORRECT
render: function() {
return (
<ul>
{
this.state.animals.map(function(animal, index) {
return (
<ListItemWrapperComponent key={index}>
<li>
{animal.name} the {animal.animal}!
</li>
</ListItemWrapperComponent>
);
})
}
</ul>
);
}
// NOT CORRECT
render: function() {
return (
<ul>
{
this.state.animals.map(function(animal, index) {
return (
<ListItemWrapperComponent>
<li key={index}>
{animal.name} the {animal.animal}!
</li>
</ListItemWrapperComponent>
);
})
}
</ul>
);
}
key
just needs to be set in the outermost component.
Read more: Recursing on Children
Read more: Reconciliation
Read more: Trade-offs
Read more: Marko vs. React: Performance Benchmark
Read more: React.js and Dynamic Children - Why the Keys are Important
Read more: React vs. Ember by Alex Matchneer
React Nested Views
React is great for working with a tree structure like HTML. You can nest to your heart's content, it's encouraged. Remember: everything is a component. It's just like working in HTML, so it should start to come naturally to you.
Consider this App
component:
React.render(<App />, document.getElementById('content'));
We render it to #content
.
Inside the App
component its render
could have an assortment of components. Maybe it looks like this:
// App.jsx
...
render: function() {
return (
<div>
<SubNavBar />
<MainContent>
</div>
);
}
...
// SubNavBar.jsx
...
render: function() {
return (
<Nav>
<SearchBar />
<NavMenuDropdown>
<NavItemLink name='Edit Profile' to='/edit' />
<NavItemLink name='Logout' to='/logout' />
</NavMenuDropdown>
</Nav>
);
}
...
// MainContent.jsx
...
render: function() {
return (
<Grid fluid>
<Col md={3}>
<Sidebar />
</Col>
<Col md={9}>
<Dashboard />
</Col>
</Grid>
);
}
...
Each component could keep going. Eventually you'd reach a point it actually returns the HTML, for example, Grid
might actually just be an abstraction for:
// Grid.jsx
render: function() {
return (
<div className={this._classNames()}>
{this.props.children}
</div>
);
}
React Mixins
Mixins are a way of sharing reusable functionality between components.
Key point from React documentation:
Components are the best way to reuse code in React, but sometimes very different components may share some common functionality. These are sometimes called cross-cutting concerns. React provides mixins to solve this problem.
NOTE: The validity of mixins, is currently debated in the community. While you may continue to use them with React.createClass
, mixins are not currently available when using ES6 classes. The community seems to be leaning toward the idea of containers aka higher-order components or decorators (functions or components which manipulate or alter other components).
Mixin example:
var Mixin1 = {
componentDidMount: function() {
console.log('Mixin1, component did mount!');
}
};
var Mixin2 = {
componentDidMount: function() {
console.log('Mixin2, component did mount!');
}
};
var Greeter = React.createClass({
mixins: [Mixin1, Mixin2],
componentDidMount: function() {
console.log('Greeter, component did mount!');
},
render: function() {
return (
<div>Hi!</div>
);
}
});
React.render(<Greeter />, document.getElementById('content'));
After componentDidMount
, the console should display:
"Mixin1, component did mount!"
"Mixin2, component did mount!"
"Greeter, component did mount!"
Mixins are a way of sharing functionality in lifecycle events between components. They can also be used to add custom methods to your React components.
Read more: Mixins
Read more: Higher-Order Components (HOC)
React Pure Render
A simple performance boost you can get out of React is through the PureRenderMixin
.
Per the React documentation:
If your React component's render function is "pure" (in other words, it renders the same result given the same props and state), you can use this mixin for a performance boost in some cases.
Under the hood, the mixin implements
shouldComponentUpdate
, in which it compares the current props and state with the next ones and returns false if the equalities pass.Note: 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. Or, consider using immutable objects to facilitate fast comparisons of nested data. Furthermore, shouldComponentUpdate skips updates for the whole component subtree. Make sure all the children components are also "pure".
Let's check out an example:
var Greeter = React.createClass({
mixins: [React.addons.PureRenderMixin],
getInitialState: function() {
return {
greeting: 'Hi'
};
},
componentDidUpdate: function() {
console.log('Component updated!');
},
render: function() {
console.log('Component rendered!');
return (
<div>
Greeting: {this.state.greeting}
<br />
<hr />
<a
href="#"
onClick={this.handleGreetingClick.bind(null, 'Hi')}
>
Say Hi
</a>
<br />
<a
href="#"
onClick={this.handleGreetingClick.bind(null, 'Hey')}
>
Say Hey
</a>
</div>
);
},
handleGreetingClick: function(greeting, e) {
e.preventDefault();
console.log('click event', greeting);
this.setState({
greeting: greeting
});
}
});
React.render(<Greeter />, document.getElementById('content'));
The initial state of this.state.greeter
is set to Hi
.
If we clicked the Say Hi action, the console would display:
"click event"
"Hi"
No updates were applied because the previous state is {greeting: 'Hi'}
, and the new state is the exact same {greeting: 'Hi'}
. If the props and state haven't visibly changed since the last render
, PureRenderMixin
will just skip the new one.
If we clicked Say Hey, the console would say the following instead:
"click event"
"Hey"
"Component rendered!"
"Component updated!"
Read more: PureRenderMixin
React and 3rd Party Libraries
The neat thing about React is you don't have to commit your whole application to using it. You can sprinkle it in and eventually... you'll want to write to everything in React. ;)
You can use third party libraries with React, even if they were not specifically written for React, some examples include jQuery UI, your favorite charting library or DataTables.
We will use DataTables as an example of how you could use it with React.
var accountingData = function() {
var data = [];
var random = (Math.floor(Math.random() * 10));
for (i = 0; i < random; i++) {
data.push({
name: 'Transaction ' + (i + 1),
amount: (Math.floor(Math.random() * 100))
});
}
return data;
};
var AccountingTable = React.createClass({
getInitialState: function() {
return {
transactions: []
};
},
componentDidMount: function() {
$(this.refs.table.getDOMNode()).DataTable();
},
componentWillUpdate: function() {
var table = $(this.refs.table.getDOMNode()).DataTable();
table.destroy();
},
componentDidUpdate: function() {
$(this.refs.table.getDOMNode()).DataTable();
},
componentWillUnmount: function() {
var table = $(this.refs.table.getDOMNode()).DataTable();
table.destroy();
},
render: function() {
return (
<div>
<table ref="table" className="table">
<thead>
<th>Transaction Name</th>
<th>Amount</th>
</thead>
<tbody>
{
this.state.transactions.map(function(trans, index) {
return (
<tr key={index}>
<td>{trans.name}</td>
<td>{trans.amount}</td>
</tr>
);
})
}
</tbody>
</table>
<a href="#" className="btn btn-default" onClick={this.handleGetTransactionsClick}>Get Transactions</a>
</div>
);
},
handleGetTransactionsClick: function(e) {
e.preventDefault();
this.setState({
transactions: accountingData()
});
}
});
React.render(<AccountingTable />, document.getElementById('content'));
This example is incredibly arbitrary. You would probably update the table via ajax instead, or write your own table component, or use something off the shelf for React like FixedDataTable from Facebook or Griddle, etc.
In this example, our initial state, this.state.transactions
, is an empty array.
After componentDidMount
, we initialize and render DataTable
.
Then on Get Transactions, we update the state
with the data.
Before the component updates, we destroy DataTable
, and reinitialize it on componentDidUpdate
. When the component unmounts we will also destroy the DataTable
to clean up any associated event handler or resource.
If there are more interactions with the table, instead of asking React for the DOM node every time, we could store a reference in something like this._dataTableRef
.
You could also have a React component which doesn't render anything to the DOM (aside from a required component root) but uses its lifecycle events to trigger commands on a third party library to manipulate the DOM. This concept is what Ryan Florence calls a portal.
Read more: React.js Conf 2015 - Hype! (Portals) - Video.
Read more: Portals Example Repo
Read more: "Portals" in React.js
React Component Parent and Child Communication
As you start to build out your React application, you may find yourself asking: how do I get this child component to update the state of my parent component?
You can simply pass down a callback function from the Parent Component to the Child Component.
For example:
var ShoppingList = React.createClass({
getInitialState: function() {
return {
items: [
{ id: 1, name: 'Apple', qty: 2 },
{ id: 2, name: 'Orange', qty: 3 },
{ id: 3, name: 'Chicken', qty: 1 }
]
};
},
render: function() {
return (
<List
items={this.state.items}
onClick={this.handleItemClick}
/>
);
},
handleItemClick: function(name, e) {
e.preventDefault();
console.log('click event', name);
}
});
var List = React.createClass({
propTypes: {
items: React.PropTypes.array.isRequired,
onClick: React.PropTypes.func.isRequired
},
render: function() {
return (
<ul>
{
this.props.items.map(function(item, index) {
return (
<li key={index}>
<a href="#" onClick={this.props.onClick.bind(null, item.name)}>
{item.name}
</a>
</li>
);
}.bind(this))
}
</ul>
);
}
});
React.render(<ShoppingList />, document.getElementById('content'));
In this example, we have a ShoppingList
parent component, that renders a List
child component.
Let's walk through the process:
ShoppingList
sets its initial state ofitems
to an array:
[
{ id: 1, name: 'Apple', qty: 2 },
{ id: 2, name: 'Orange', qty: 3 },
{ id: 3, name: 'Chicken', qty: 1 }
]
-
It passes
this.state.items
to theList
component, along with the callback function#handleItemClick
. -
List
component, expects thepropTypes
ofitems
(array) andonClick
(function), both are required. -
List
creates dynamic children via map, which is binded. It returns<li>
tags, passing in the index as thekey
. Inside of the<li>
tag, is an<a>
tag with anonClick
event, where we pass thethis.props.onClick
callback function we were given from the parent. We also bindthis.props.onClick
to get back theitem.name
.
Upon clicking Apple, you should see in the console:
"click event"
"Apple"
Then #setState
is called, which will update activeItem
to whatever the name of the item that was clicked.
React Fetching Data
This example will show you how you could fetch data from inside your component. As your application grows and becomes more complex, you will probably want to use Flux.
In this example, we have an API that we can query to retrieve a collection of videos that we want to display. We want to be able to switch between videos, display its metadata (timestamp, title, etc) and show what video we are on and what the total videos are.
Note: For the sake of this example, we will use jQuery to help ease you into React. jQuery is absolutely not necessary, but for the convenience of making a AJAX request it will be used.
Here is a mockup of what we want to create:
+-----------------------------------------------------------+
| [App] |
| |
| +------------------------------------------------------+ |
| | Title (Current Video Index / Total Videos) | |
| | | |
| |[HeaderView] | |
| +------------------------------------------------------+ |
| +------------------------------------------------------+ |
| |[StageView] | |
| | +--------------------------------+ | |
| | | Video | | |
| | | | | |
| | Previous | | Next | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | +--------------------------------+ | |
| | +--------------------------------+ | |
| | | Metadata | | |
| | +--------------------------------+ | |
| +------------------------------------------------------+ |
+-----------------------------------------------------------+
Components have been marked with [brackets].
- [App] sets its initial state
- [App] makes a AJAX request on mount
- [App] renders [HeaderView] and [StageView]
- [HeaderView] receives a title, current video index and total videos via props and renders
- [StageView] receives a video and callbacks for handling previous/next events and renders
The code:
var App = React.createClass({
getInitialState: function(){
return {
videoData: [],
currentVideo: 0,
totalVideos: 0
}
},
componentDidMount: function(){
this._retrieveVideos();
},
render: function(){
return(
<div>
<HeaderView title="Jetclips" currentVideo={this.state.currentVideo +1} totalVideos={this.state.videoData.length}/>
<StageView onChange={this._updateIndex} video={this.state.videoData[this.state.currentVideo] || {}} />
</div>
);
},
_retrieveVideos: function(){
//Ajax
$.getJSON( "http://jetclips.herokuapp.com/api/v1/videos/489159771140559", function( data ) {
this.setState({videoData: data});
}.bind(this));
},
_updateIndex: function(n){
this.setState({currentVideo: this.state.currentVideo + n});
}
});
var HeaderView = React.createClass({
propTypes: {
title: React.PropTypes.string,
currentVideo: React.PropTypes.number,
totalVideos: React.PropTypes.number
},
render: function() {
return (
<div className="header">
<h1>{this.props.title} <small>{this.props.currentVideo} / {this.props.totalVideos}</small></h1>
</div>
);
}
});
var StageView = React.createClass({
propTypes: {
video: React.PropTypes.object.isRequired,
onChange: React.PropTypes.func.isRequired
},
componentDidMount: function(){
$("body").keydown(this._handleArrowKeys);
},
componentWillUnmount: function() {
$("body").off(this._handleArrowKeys);
},
render: function() {
return (
<div className="container">
<div className="col-md-2">
<a href="#" onClick={this.prevVideo}>Previous</a>
</div>
<div className="videoContainer col-md-8">
<video controls src={this.props.video.source}></video>
<div className="video-metadata">
<span>{this.props.video.name || 'NA'}</span><br />
<span>{this.props.video.ts}</span><br />
<span>{this.props.video.id}</span><br />
</div>
</div>
<div className="col-md-2">
<a href="#" onClick={this.nextVideo}>Next</a>
</div>
</div>
);
},
nextVideo: function(e){
e.preventDefault();
this.props.onChange(1);
},
prevVideo: function(e){
e.preventDefault();
this.props.onChange(-1);
},
_handleArrowKeys: function(e) {
if (e.keyCode === 37) {
this.prevVideo(e);
};
if (e.keyCode === 39) {
this.nextVideo(e);
}
}
});
React.render(<App />, document.getElementById('content'));
You can see that [App] is the entry point, the AJAX request gets called, and then we pass down the relevant props down to [HeaderView] and [StageView].
If you commented out #_retrieveVideos
inside [App], you would see there wouldn't be anything there.
You can also see that a #_updateIndex
function gets passed down to [StageView] to manage the moving between videos.
Also if you look at [StageView], when the component mounts we attach a #keydown
event listener to the body so we can move between videos when the left or right arrow key is pressed.
Remember: When attaching events on mount, you should clean them up on componentWillUnmount
.
Feel free to mess around with this example and experiment with things! One area of improvement is the logic for [App]'s #_updateIndex
. There should be guards to prevent one from going back past the first video and the last or perhaps it should loopback.
React Developer Tools
React has a handy developer debug tool, for Chrome, check it out: React Developer Tools.
Harmony aka ES6 aka ES2015
Initially known as Harmony or ES6, ES2015 is an update to the ECMAScript standard.
This document will refer to the new upcoming standard for ECMAScript as ES6.
While ES6 browser support is still in progress, many developers are taking advantage of it today. They are doing so through the use of a transpiler like Babel. Babel will take your ES6 code and convert it into ES5, so developers can start taking advantage of ES6 while browsers work to implement the ES6 standard.
This section will cover some of the new syntax you may commonly see in React projects taking advantage of ES6.
It is recommended you review one of the following overviews (or maybe look at all of them) to get a better understanding about ES6's features.
Read more: ECMAScript 6 Overview
Read more: Using ECMAScript 6 today
Read more: You Don't Know JS: ES6 & Beyond
Read more: ES6 compatibility table
Modules
If you are unfamiliar with modules, it is a way of declaring dependencies. The idea is similar to require
in Ruby or import
in Python.
Read more: ECMAScript 6 modules: the final syntax
Read more: Understanding Javascript Modules
Read more: Node.js modules - Review this if you are unfamiliar with node modules.
import
You may already be familiar with CommonJS modules, especially if you have done work with Node.js.
To refresh your memory, it looks like this:
var React = require('react');
var MyComponent = React.createClass(...);
You can now instead do:
import React from 'react';
To import a specific function:
// ES6
import { Grid, Row, Column } from 'react-bootstrap';
// CommonJS Equivalent
var ReactBootstrap = require('react-bootstrap');
var Grid = ReactBootstrap.Grid;
var Row = ReactBootstrap.Row;
var Column = ReactBootstrap.Column;
export
To specify what you want to export by default, observe the following:
// MyReactComponent.jsx
//ES6
export default MyReactComponent;
// CommonJS Equivalent
module.exports = MyReactComponent;
// Import the default
import MyReactComponent from './MyReactComponent';
You can also export multiple things.
// MyReactComponent.jsx
export SOME_CONST;
export someFunction;
export default MyReactComponent;
// Import something specific
import { SOME_CONST, someFunction } from './MyReactComponent';
Variable Assignment
var
can be replaced with either let
or const
. In most cases, you will probably want to use const
.
Read more: Let + Const
const
const
is single-assignment. This means that it is read-only.
MDN explanation:
Summary
The const declaration creates a read-only named constant.
Description
This declaration creates a constant that can be global or local to the function in which it is declared. Constants are block-scoped. The value of a constant cannot change through re-assignment, and a constant cannot be re-declared. An initializer for a constant is required. A constant cannot share its name with a function or a variable in the same scope.
const test = "x";
// error cant reassign a const
test = "y";
const test = {key: 'value'};
// OK (object attributes are not protected)
test.key2 = '1';
Read more: MDN - const
let
MDN explanation:
Summary
The let statement declares a block scope local variable, optionally initializing it to a value.
Description
let allows you to declare variables that are limited in scope to the block, statement, or expression on which it is used. This is unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope.
Example from MDN:
function letTest() {
let x = 31;
if (true) {
let x = 71; // different variable
console.log(x); // 71
}
console.log(x); // 31
}
Read more: MDN - let
Fat Arrow
The arrow function, or fat arrow works similar to how it does in Coffeescript.
According to MDN:
An arrow function expression (also known as fat arrow function) has a shorter syntax compared to function expressions and lexically binds the this value. Arrow functions are always anonymous.
Example:
const ListComponent = React.createClass({
propTypes: {
items: React.PropTypes.array
},
getInitialState: function() {
return { starredItemIndex: 0 }
},
render: function() {
return (
<ul>
{
this.props.items.map((item, index) => {
const isStarred = this.state.starredItemIndex === index ? 'starred' : null;
return (
<li className={isStarred}>item.name</li>
);
})
}
</ul>
);
}
});
In the above example, you can see how we call the fat arrow within the map, instead of having to #bind(this)
, so we have access to get state#starredItemIndex
.
this.props.items.map(function(item, index) {
...
}).bind(this)
or doing something like:
const self = this;
return (
<ul>
{
this.props.items.map(function(item, index) {
const isStarred = self.state.starredItemIndex...
})
}
</ul>
);
Read more:
Arrow functions and their scope
Enhanced Object Literals
ES6 brings some additional conveniences. es6features by lukehoban sums it up nicely:
Object literals are extended to support setting the prototype at construction, shorthand for foo: foo assignments, defining methods, making super calls, and computing property names with expressions. Together, these also bring object literals and class declarations closer together, and let object-based design benefit from some of the same conveniences.
Example from es6features:
var obj = {
// __proto__
__proto__: theProtoObj,
// Shorthand for ‘handler: handler’
handler,
// Methods
toString() {
// Super calls
return "d " + super.toString();
},
// Computed (dynamic) property names
[ 'prop_' + (() => 42)() ]: 42
};
With ES6 React components, you will most likely see the function shorthand being used.
Example:
const ListComponent = React.createClass({
render() {
return (
<ul>
...
</ul>
);
}
});
Instead of doing render: function() { ... }
, the shorthand render() { ... }
can be used.
Read more:
es6features - Enhanced Object Literals
Object-Based JavaScript in ES6
Spread Operator
Classes
Babel
Useful Libraries
SuperAgent
SuperAgent is a small progressive client-side HTTP request library, and Node.js module with the same API, sporting many high-level HTTP client features.
Bluebird
Bluebird is a fully featured promise library with a focus on innovative features and performance.
lodash
lodash is a JavaScript utility library delivering consistency, modularity, performance & extras.
normalizr
normalizr normalizes deeply nested JSON API responses according to a schema for Flux applications.
Webpack
Hot Module Replacement and Hot Reloading
React Router
Flux
Alt
Alt Actions
Alt Stores
Testing
Mocha
LICENSE
Copyright (c) 2015 Michael Chau.
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3
or any later version published by the Free Software Foundation;
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
A copy of the license is included in the section entitled "GNU
Free Documentation License".