Rails Meets React Sample
Rails Meets React Sample
To master React, you need to know the components structure. Basically, components in React are
made of three basic parts:
state - React components maintain their dynamic part of data separated from the static data
for performance reasons. Manipulating components is done by changing the state doing so
forces React to re-render a component.
properties - React components get properties in the moment they are instantiated. You can
use them to create the initial state and set values that need to be accessible and are constant
through the whole lifecycle of a component.
render method - The render method should always return a React component. It has access
to properties and state and they (and only they) decide what will be the outcome of the render
method. The render method should only generate HTML and assign key/refs/event handlers
to it. If you generate it using only state and properties, you achieve what React developers call
side-effect free render method. You should aim to do so every time it makes React faster,
makes your development and testing easier.
Thats it. These three parts need to be present in all React components you create. Of course it is not
all - React components come with some additional features that will be discussed later.
State
In the world of dynamic user interfaces the user interface is stateless statement (which template
libraries like Handlebars or Mustache repeat like a mantra) is hard to defend the more complex
UI, the harder it may be to defend it. React developers know it thats why they made components
state one of the most important concepts in the whole library.
76
Changing state should be the only way to re-render your component, resulting in a visible change
state is available during rendering of the component and in lifecycle methods of it (discussed later).
You may think of it as dynamic data of a component with an additional side-effect: Changing
state forces re-rendering of component.
By default, when instantiating a component, the state is empty it is a null value. You can change
it using the setState method which every component has. Note that you cannot manipulate
the state of unmounted components. All components start as unmounted you must explicitly
call the React.renderComponent prototype method to make it mounted which allows you to use
setState and other components instance methods.
State is accessible in most component methods under @state instance variable. In the Greeter
component you directly access the name field of the @state inside render method.
Common mistakes
Consider this small snippet of code.
Greeter = React.createClass
render: ->
React.DOM.span
className: 'greeter-message'
"Hello, #{@state.name}!"
greeter = React.createFactory(Greeter)
element = greeter()
element.setState(name: 'John') # ?
In this case the setState invocation results in an error since elements do not have state. Only the
rendered components have state. Calling setState in such situation will result in an error.
Here is another, more tricky example.
77
Greeter = React.createClass
render: ->
React.DOM.span
className: 'greeter-message'
"Hello, #{@state.name}!"
greeter = React.createFactory(Greeter)
component = React.render(greeter(), document.getElementById("greeter-placeholder"))
component.setState(name: 'my first React component!') # ?
React.render will throw the null is not an object exception. Why is that? Because
React.render calls the render method from the component that is being mounted. Remember
that @state by default is null. So calling @state.name inside the render method raises an error
in such situation.
If render finished successfully itd be absolutely fine to ask for the state change. To fix this code,
you need to know how to provide the initial state to our components. Lets see how to do this.
After this change to the Greeter component our last example is going to work as expected:
78
greeter = React.createFactory(Greeter)
component = React.render(greeter(), document.getElementById("greeter-placeholder"))
# <span class='greeter-message'>Hello, World!</span> will be
# placed inside #greeter-placeholder element, overriding previous content.
component.setState(name: 'my first React component!')
# Component is re-rendered, so it results in "Hello, my first React component!"
# as opposed to "Hello, World!" you had before.
The important part about getInitialState is that you can use properties to instantiate the initial
state. That means you can easily make defaults overridden.
Properties
You have seen that you can go quite deep even without knowing what properties are. But properties
are a very helpful part of all React components you are going to create. The basic idea is that they
79
are data which have to be unchanged during the whole lifecycle of the component whether to
instantiate a default state or to reference this data while defining components behaviour and the
render outcome. You may think about them as constants in your component you may reference
them during rendering or setting components behaviour, but you shouldnt change them.
React developers often say that properties are immutable. That means that after construction of
the component they stay the unchanged. Forever. If its a complex object you can of course invoke
methods on it but it is only because its hard to ensure for React that something changed. React
assumes that changes to properties never happen. Changing properties, even by invoking methods
on it is considered an anti-pattern.
You pass properties during construction of an element. Basically, every factory is a function with
the following signature:
factory(properties, children)
Properties are accessible even in elements via the @props field. As I mentioned before, you can also
use them in getInitialState to parametrize default state creation:
Example: Passing a parameter to instantiate default state
Greeter = React.createClass
getInitialState: ->
name: @props.initialName || "World"
render: ->
React.DOM.span
className: 'greeter-message'
"Hello, #{@state.name}!"
rootNode = document.getElementById("greeter-box")
greeter = React.createFactory(Greeter)
component = React.render(greeter(initialName: "Andy"), rootNode)
# <span class='greeter-message'>Hello, Andy!</span>
component.setState(name: 'Marcin')
# <span class='greeter-message'>Hello, Marcin!</span>
React.unmountComponentAtNode(rootNode)
component = React.render(greeter(), rootNode)
# <span class='greeter-message'>Hello, World!</span>
This use case is a quite common usage of properties. Another common usage is to pass dependencies
to components:
80
Example: Metrics tracking tool needs to be notified about user decision of reading full content of blogpost.
DOM = React.DOM
BlogPost = React.createClass
getInitialState: ->
fullContentVisible: false
render: ->
DOM.div
className: 'blog-content'
DOM.h1
key: 'blog-title'
className: 'title'
@props.blogpost.title
DOM.p
key: 'blog-lead'
className: 'lead'
@props.blogpost.lead
DOM.a
key: 'blog-more'
href: "#!/more"
onClick: @continueReadingClicked
"Continue reading ->"
if @state.fullContentVisible
DOM.div
key: 'blog-full-content'
@props.blogpost.fullContent
continueReadingClicked: (event) ->
unless @state.fullContentVisible
@props.metricsAdapter.track('full-content-hit')
@setState fullContentVisible: true
blogPost = React.createFactory(BlogPost)
post = blogPost(metricsAdapter: @googleAnalyticsAdapter)
componentInstance = React.renderComponent(post, document.getElementById('blog'))
Properties allow you to store all references that are important for our component, but do not change
over time.
You can use properties alone to create React components for your static views. Such components are
called stateless components and should be used as often as possible.
81
Stateless components
In our previous examples you were using components from the React library which are provided
to create basic HTML tags you know and use in static pages. If you look at them closely you may
notice that they are stateless. You pass only properties to elements of their type and they maintain
no state. State is an inherent part of more dynamic parts of your code. However it is advisable to
keep display data only components stateless. It makes code more understandable and testable and
its generally a good practice.
Displaying person information does not need state at all
DOM = React.DOM
PersonInformation = React.createClass
person: ->
@props.person
render: ->
DOM.div
className: 'person-info'
DOM.header
key: 'info-header'
DOM.img
key: 'avatar'
className: 'person-avatar'
src: @person().avatarUrl
alt: ''
DOM.h2
key: 'full-name'
className: 'person-name'
@person().fullName
82
You may also find it useful to use this method to improve declarativeness of your render method
with this neat trick:
Avoiding conditionals in render method would make it shorter and easier to comprehend.
OnOffCheckboxWithLabel = React.createClass
getDefaultProps: ->
onLabel: "On"
offLabel: "Off"
id: 'toggle'
initiallyToggled: false
getInitialState: ->
toggled: @props.initiallyToggled
toggle: ->
@setState toggled: !@state.toggled
render: ->
React.DOM.div
className: 'on-off-checkbox'
React.DOM.label
key: 'label'
83
htmlFor: @props.id
if @state.toggled
@props.onLabel
else
@props.offLabel
React.DOM.input
key: 'checkbox'
type: 'checkbox'
id: @props.id
checked: @state.toggled
onChange: @toggle
84
You can use our on/off toggle with more sophisticated labels for free with default properties approach.
coolFeatureToggle = OnOffCheckboxWithLabel
labels:
true: "Cool feature enabled"
false: "Cool feature disabled"
id: "cool-feature-toggle"
As you can see, relying on default properties can have many advantages for your component. If you
do not want to have configurable messages, you can create a method inside the component and put
the labels object there. I like to have sensible defaults and to provide ability to change it to match
my future needs.
Component children
Lets look at the React factory arguments once again:
factory(properties, children)
While you already have detailed knowledge about properties, the children argument remains a
mystery until now. As you may know, HTML of your web page forms a tree structure you have
HTML tags nested in another tags, which nest another tags and so on. React components can have
exactly the same structure and thats what the children attribute is for. You can pass children
components as a second argument of your component its up to you what youll do with it.
Typically you pass an array of components or a single component there. Such children components
are available in a special property called children:
Using children in components from React.DOM namespace.
React.DOM.div
className: 'on-off-checkbox'
React.DOM.label
key: 'label'
htmlFor: @props.id
if @state.toggled
@props.onLabel
else
@props.offLabel
React.DOM.input
key: 'checkbox'
type: 'checkbox'
id: @props.id
85
checked: @state.toggled
onChange: @toggle
# equals:
React.DOM.div({className: 'on-off-checkbox'}, [React.DOM.label(...), React.DOM.input(...)])
You can access children using a special children field inside properties.
WhoIsInvited = React.createClass
render: ->
React.DOM.div
className: 'who-is-invited-box'
React.DOM.h3
key: 'header'
className: 'who-is-invited-header'
"You've invited this people to tonight's pajama party:"
React.DOM.ul
key: 'invited-list'
className: 'who-is-invited-list'
for personInformationComponent in @props.children
React.DOM.li
key: "person-#{personInformationComponent.person().id}"
className: 'who-is-invited-list-item'
personInformationComponent
whoIsInvited = React.createFactory(WhoIsInvited)
invitations = whoIsInvited({}, [KidInformation(...), AdultInformation(...), AdultInformati\
on(...)])
# @props children will contain
# [KidInformation(...), AdultInformation(...), AdultInformation(...)] array.
This feature allows you to easily create some kind of wrapper components (I call it open components)
like a list above. Its quite common to have these kind of user interface elements many wellestablished generic UI solutions can be implemented this way. For example accordion boxes,
drop-down menus, modals all of these generic solutions you know and use can follow this pattern,
since they are generally containers to our content.
86
Properties and state are a way of React component to handle data which can be a result of user actions
or events from the external world (messaging mechanism like Pusher, AJAX calls, websockets, etc.).
While state is all about dynamism of components (and thats why you have switched from
Rails views to frontend applications after all), properties allow you to provide data for more
static purposes in our component.
render method
Last, but not least, all React components must have the render method. This method always
returns a React component. Since you compose your component from smaller components
(with components provided in React.DOM namespace as leafs of your components tree) it is an
understandable constraint here.
Since React 0.12, it can return false or null as well. In such case React will render an invisible
<noscript> tag.
What is important, you should never call the render method explicitly. This method is called
automatically by React in these two situations:
If the state changes. It also calls componentDidUpdate method.
If you mount component using React.render. It also calls the componentDidMount method.
The lifecycle methods such as the mentioned componentDidMount and componentDidUpdate will
be discussed later in the book.
An important assumption that React makes about your render method is that it is idempotent that means, calling it multiple times with the same properties and state will always result in the
same outcome.
React components style (especially the built-in React.DOM components) resembles HTML builders
a bit. It might look like Haml to you and it is basically the same concept. You create components
which are transformed into your HTML.
87
What you achieve with state and properties is dynamic nature of this HTML - you can click on
something to make an action, change the rendering of this HTML and so on. It looks really familiar
to what you may find in Haml view files:
Example of Haml view
%section.container
%h1= post.title
%h2= post.subtitle
.content
= post.content
React counterpart
DOM = React.DOM
# ...
DOM.section(className: 'container',
DOM.h1({}, @state.post.title),
DOM.h2({}, @state.post.subtitle),
DOM.div(className: 'content', @state.post.content)
88
React.DOM.div
className: 'greeter-box'
children
greeterSpan: (children) ->
React.DOM.span
className: 'greeter-text'
children
Creating components based on properties and state. Since you should never store components
in state or properties, the React way is to construct components inside the render method.
Creating components based on properties and state.
BooksListItem = React.createClass
render: ->
React.DOM.li({}, @props.book.name)
booksListItem = React.createFactory(BooksListItem)
BooksList = React.createClass
render: ->
React.DOM.ul({className: 'book-list'}, [
for book in @props.books
booksListItem({book: book})
# Above you create component from books in our properties.
])
Define the HTML structure of a user interface part. That means you can compose the return
value from React.DOM components directly or with higher level components. Eventually it is
transformed into HTML.
Do not try to memoize data in the render method. Especially components. React is managing
the components lifecycle using its internal algorithm. Trying to re-use component by hand is asking
for trouble, since it is easy to violate React rules for re-rendering here.
JSX
JSX is a JavaScript syntax extension created by React developers to make declaration of components
similar to HTML. It fixes the pain of creating components in a strict and unwieldy JavaScript syntax,
since React is used by their creators in a JavaScript environment. In my personal opinion, since
Rails comes with CoffeeScript, you dont need it. CoffeeScript syntax is flexible enough to declare
React components in a neat way. Building the render method without JSX in CoffeeScript makes
it more similar to Haml than to ERB. Even if you decide you dont need JSX, you must still know
89
how to desugar JSX to a real code. It is because most examples youll find on the Internet for React
are JSX-based. Without this skill you can take little value from even valuable sources of information
about React.
90
You can omit the parentheses and rely on a CoffeeScripts object syntax to list properties in a
more readable way.
Using coffeescript object syntax
R = React.DOM
PostCounter = React.createClass
displayName: 'Post Counter'
render: ->
R.div
className: 'post-counter'
[
"This month there are #{@props.postCount} published posts already.",
R.a
key: 'cta'
href: '/posts/new'
"New post"
]
91
key: 'cta'
href: '/posts/new'
"New post"
Note that if you dont pass any properties youll have a syntax error trying to pass children
this way.
There is a problem if you dont pass properties to a component
R = React.DOM
PostCounter = React.createClass
displayName: 'Post Counter'
render: ->
R.div
"This month there are #{@props.postCount} published posts already."
R.a
key: 'cta'
href: '/posts/new'
"New post"
# SYNTAX ERROR!
The most elegant workaround Ive found is to restore square brackets in a collection and put
it in a children property explicitly (notice you still dont need commas between collection
elements):
Passing to children explicitly and restoring square brackets is the most elegant way Ive found to solve
this issue.
R = React.DOM
PostCounter = React.createClass
displayName: 'Post Counter'
render: ->
R.div
# Passed children components to `children` property explicitly.
children: [
"This month there are #{@props.postCount} published posts already."
R.a
key: 'cta'
href: '/posts/new'
"New post"
]
Create many small methods to hide logical parts of your component and provide a sensible
naming. It is a great way to improve readability of your React code.
92
R = React.DOM
PostCounter = React.createClass
displayName: 'Post Counter'
render: ->
@postCounterBox [
@postCountThisMonthMessage()
@callToActionLink()
]
postCounterBox: (children) ->
R.div
children: children
postCountThisMonthMessage: ->
"This month there are #{@props.postCount} published posts already."
callToActionLink: ->
R.a
key: 'cta'
href: '/posts/new'
"New post"
These practices are working well in my projects, especially if I work on bigger components.
Desugaring JSX
Desugaring JSX is quite easy. Consider the following JSX example (taken from the official documentation):
JSX example to desugar
<div>
<ProfilePic key="pic" username={this.props.username} />
<ProfileLink key="link" username={this.props.username} />
</div>
93
You can also use JSX compiler from the official site. Beware: it produces JavaScript as the output so
you need to transform it to CoffeeScript then. Thats why I recommend to learn desugaring manually.
If you understand it well itll be a no-brainer for you.
Summary
In this chapter you learned about the three main building blocks of React components state,
properties and render method. This alone is a very complete knowledge that you can use straight
away. Since you know how to create a component, feed it with data and manipulate it, you can
make fairly complex components even now. What you lack is mastering how to handle actions to
user and the external world. The next chapter is all about it.
HTML
94
<div class="basket">
<table>
<thead>
<th>Product name</th>
<th>Price</th>
<th>Tax</th>
</thead>
<tbody>
<tr>
<td>Product 1</td>
<td>$12.00</td>
<td>$1.00</td>
</tr>
<tr>
<td>Product 2</td>
<td colspan="2">Unavailable</td>
</tr>
</tbody>
</table>
</div>
JS
var AvailableProductComponent = React.createClass({
render: function(){
return React.DOM.tr(null,
React.DOM.td({ key: 'name' }, this.props.name),
React.DOM.td({ key: 'price' }, this.props.price),
React.DOM.td({ key: 'tax' }, this.props.tax)
);
}
});
var availableProductComponent = React.createFactory(AvailableProductComponent);
var UnavailableProductComponent = React.createClass({
render: function(){
return React.DOM.tr(null,
React.DOM.td({ key: 'name' }, this.props.name),
React.DOM.td({ key: 'unavailable', colSpan: 2 }, "Unavailable")
);
}
});
var unavailableProductComponent = React.createFactory(UnavailableProductComponent);
var BasketComponent = React.createClass({
95
render: function(){
return React.DOM.div({ className: "basket" },
React.DOM.table({ key: 'table' },
React.DOM.thead({ key: 'head' },
this.props.headers.map(function(header){ return React.DOM.th({ key: "th-" + head\
er }, header) })
),
React.DOM.tbody({ key: 'body' },
availableProductComponent({ key: 'available-product', name: "Product 1", price: \
"$12.00", tax: "$1.00" }),
unavailableProductComponent({ key: 'unavailable-product', name: "Product 2" })
)
)
);
}
});
var basketComponent = React.createFactory(BasketComponent);
React.render(basketComponent({
headers: ["Product name", "Price", "Tax"]
}), $('div')[0]);
JS + JSX
var AvailableProductComponent = React.createClass({
render: function(){
return <tr>
<td key="name">{this.props.name}</td>
<td key="price">{this.props.price}</td>
<td key="tax">{this.props.tax}</td>
</tr>;
}
});
var UnavailableProductComponent = React.createClass({
render: function(){
return <tr>
<td key="name">{this.props.name}</td>
<td key="unavailable" colSpan="2">Unavailable</td>
</tr>;
}
});
var BasketComponent = React.createClass({
render: function(){
96
CoffeeScript
AvailableProductComponent = React.createClass
render: ->
React.DOM.tr null,
React.DOM.td(key: 'name', @props.name)
React.DOM.td(key: 'price', @props.price)
React.DOM.td(key: 'tax', @props.tax)
availableProductComponent = React.createFactory(AvailableProductComponent)
UnavailableProductComponent = React.createClass
render: ->
React.DOM.tr null,
React.DOM.td key: 'name', @props.name
React.DOM.td
key: 'unavailable'
colSpan: 2
"Unavailable"
unavailableProductComponent = React.createFactory(UnavailableProductComponent)
BasketComponent = React.createClass
render: ->
React.DOM.div
className: "basket",
97