We’ll go over all of the dependencies being loaded under the tag later. The main HTML
document is these few lines here:
Popular Products
For this project, we’re using Semantic UI¹⁵ for styling.
Semantic UI is a CSS framework, much like Twitter’s Bootstrap¹⁶. It provides us with a grid
system and some simple styling. You don’t need to know Semantic UI in order to use this
book. We’ll provide all of the styling code that you need. At some point, you might want
to check out the docs Semantic UI docs¹⁷ to get familiar with the framework and explore
how you can use it in your own projects.
The class attributes here are just concerned with style and are safe to ignore. Stripping those away,
our core markup is quite succinct:
¹⁵http://semantic-ui.com/
¹⁶http://getbootstrap.com/
¹⁷http://semantic-ui.com/introduction/getting-started.html
Your first React Web Application 8
Popular Products
We have a title for the page (h1) and a div with an id of content. This div is where we will ultimately
mount our React app. We’ll see shortly what that means.
The next few lines tell the browser what JavaScript to load. To start building our own application,
let’s remove the ./app-complete.js script tag completely. The comments in the code indicate which
line to remove:
After we save our updated index.html and reload the web browser, we’ll see that our app has
disappeared.
Our first component
Building a React app is all about components. An individual React component can be thought of as
a UI component in an app. Let’s take a look at the components in our app:
The app’s components
We have a hierarchy of one parent component and many sub-components:
Your first React Web Application 9
1. ProductList (red): Contains a list of product components
2. Product (orange): Displays a given product
Not only do React components map cleanly to UI components, but they are self-contained. The
markup, view logic, and often component-specific style is all housed in one place. This feature makes
React components reusable. Furthermore, as we’ll see in this chapter and throughout this course,
React’s paradigm for component data flow and interactivity is rigidly defined. We’ll see how this
well-defined system is beneficial.
The classic UI paradigm is to manipulate the DOM with JavaScript directly. As complexity of an
app grows, this can lead to all sorts of inconsistencies and headaches around managing state and
transitions. In React, when the inputs for a component change, the framework simply re-renders
that component. This gives us a robust UI consistency guarantee:
With a given set of inputs, the output (how the component looks on the page) will always be
the same.
Let’s start off by building the ProductList component. We’ll write all of our React code for this
section inside the file app.js. Let’s open the app.js file and insert the component:
const ProductList = React.createClass({
render: function () {
return (
Hello, friend! I am a basic React component.
);
},
});
ES6: Prefer const and let over var
Both the const and let statements declare variables. They are introduced in ES6.
const is a superior declaration in cases where a variable is never re-assigned. Nowhere else in the
code will we re-assign the ProductList variable. Using const makes this clear to the reader. It
refers to the “constant” state of the variable in the context it is defined within.
If the variable will be re-assigned, use let.
If you’ve worked with JavaScript before, you’re likely used to seeing variables declared with var:
var ProductList = ...
We encourage the use of const and let instead of var. In addition to the restriction introduced by
Your first React Web Application 10
const, both const and let are block scoped as opposed to function scoped. Typically this separation
of scope helps avoid unexpected bugs.
React.createClass()
To create a component, we use the function React.createClass(). This is how all components are
defined in React. We pass in a single argument to this function: a JavaScript object.
This class definition object (the argument we pass to the React.createClass() method) in our case
has just one key, render, which defines a rendering function. render() is the only required method
for a React component. React uses the return value from this method to determine what exactly to
render to the page.
The createClass() method is one way to create a React component. The other main way
of defining one is by using the ES6 classical implementation:
class ProductList extends React.Component {}
We’ll primarily be using the createClass() method throughout this course.
If you have some familiarity with JavaScript, the return value may be surprising:
return (
Hello, friend! I am a basic React component.
);
The syntax of the return value doesn’t look like traditional JavaScript. We’re using JSX (JavaScript
eXtension syntax), a syntax extension for JavaScript written by Facebook. Using JSX enables us to
write the markup for our component views in a familiar, HTML-like syntax. In the end, this JSX
code compiles to vanilla JavaScript. Although using JSX is not a necessity, we’ll use it in this course
as it pairs really well with React.
If you don’t have much familiarity with JavaScript, we recommend you follow along and
use JSX in your React code too. You’ll learn the boundaries between JSX and JavaScript
with experience.
Your first React Web Application 11
JSX
React components ultimately render HTML which is displayed in the browser. As such, the render()
method of a component needs to describe how the view should be represented as HTML. React
builds our apps with a fake representation of the Document Object Model (DOM, for short, refers
to the browser’s HTML tree that makes up a web page). React calls this the virtual DOM. Without
getting deep into details for now, React allows us to describe a component’s HTML representation
in JavaScript.
JSX was created to make this JavaScript representation of HTML more HTML-like. To understand
the difference between HTML and JSX, consider this JavaScript syntax:
React.createElement('div', {className: 'ui items'},
'Hello, friend! I am a basic React component.'
)
Which can be represented in JSX as:
Hello, friend! I am a basic React component.
The code readability is slightly improved in the latter example. This is exacerbated in a nested tree
structure:
React.createElement('div', {className: 'ui items'},
React.createElement('p', null, 'Hello, friend! I am a basic React component.')
)
In JSX:
Hello, friend! I am a basic React component.
JSX presents a light abstraction over the JavaScript version, yet the legibility benefits are huge.
Readability boosts our app’s longevity and makes it easier to onboard new developers.
Even though the JSX above looks exactly like HTML, it’s important to remember that JSX
is actually just compiled into JavaScript (ex: React.createElement('div')).
During runtime React takes care of rendering the actual HTML in the browser for each
component.
Your first React Web Application 12
The developer console
Our first component is written and we now know that it uses a special flavor of JavaScript called
JSX for improved readability.
After editing and saving our app.js, let’s refresh the page in our web browser and see what changed:
Nothing?
Every major browser comes with a toolkit that helps developers working on JavaScript code. A
central part of this toolkit is a console. Think of the console as JavaScript’s primary communication
medium back to the developer. If JavaScript encounters any errors in its execution, it will alert you
in this developer console.
To open the console in Chrome, navigate to View > Developer > JavaScript Console.
Or, just press Command + Option + J on a Mac or Control + Shift + L on Windows/Linux.
Opening the console, we are given a cryptic clue:
Uncaught SyntaxError: Unexpected token <
Your first React Web Application 13
Error in the console
This SyntaxError prevented our code from running. A SyntaxError is thrown when the JavaScript
engine encounters tokens or token order that doesn’t conform to the syntax of the language when
parsing code. This error type indicates some code is out of place or mistyped.
The issue? Our browser’s JavaScript parser exploded when it encountered the JSX. The parser doesn’t
know anything about JSX. As far as it is concerned, this < is completely out of place.
We know that JSX is an extension to standard JavaScript. Let’s empower our browser’s plain old
JavaScript interpreter to use this extension.
Babel
Babel is a JavaScript transpiler. For those familiar with ES6 JavaScript, Babel turns ES6/ES7 code
into ES5 code so that our browser can use lots of these features with browsers that only understand
ES5.
For our purposes, a handy feature of Babel is that it understands JSX. Babel compiles our JSX into
vanilla JS that our browser can then interpret and execute. Let’s tell the browser’s JS engine we want
to use babel to compile and run our JavaScript code.
The sample code’s index.html already imports Babel in the head tags of index.html:
Your first React Web Application 14
All we need to do is tell our JavaScript runtime that our code should be compiled by Babel. We can
do this by setting the type attribute when we import the script in index.html from text/javascript
to text/babel. Open index.html and change this line:
Save index.html and reload the page.
Still nothing. However, the console reveals we no longer have any JavaScript compilation errors.
Babel successfully compiled our JSX into JS and our browser was able to run that JS without any
issues.
If your console encounters an error, check that the file has been saved before reloading the
browser. If an error still persists, check the code previously added to make sure there aren’t
any syntactical mistakes.
So what’s happening? We’ve defined the component, but we haven’t yet told React to do anything
with it yet. We need to tell the React framework that we want to run our app.
ReactDOM.render()
We’re going to instruct React to render this ProductList inside a specific DOM node.
Add the following code below the component inside app.js:
Your first React Web Application 15
ReactDOM.render(
,
document.getElementById('content')
);
We pass in two arguments to the ReactDOM.render() method. The first argument is what we’d like
to render. Here, we’re passing in a reference to our React component ProductList in JSX. The second
argument is where to render it. We’ll send a reference to the browser’s DOM element.
ReactDOM.render([what], [where]);
In our code, we have a difference in casing between the different types of React element declarations.
We have HTML DOM elements like
and a React component called . In React,
native HTML elements always start with a lowercase letter whereas React component names always
start with an uppercase letter.
With ReactDOM.render() now at the end of app.js, save the file and refresh the page in the browser:
We successfully implemented a React component in JSX, ensured it was being compiled to JS, and
rendered it in the DOM in the web browser.
Our second component
Currently, our only component is ProductList. We’ll want ProductList to render a list of products
(its “sub-components” or “child components”). In HTML, we could render this entirely in the JSX
Your first React Web Application 16
that ProductList returns. Although this works, we don’t get any benefit from encoding our entire
app in a single component. Just like we can embed HTML elements in the JSX of our components,
we can embed other React components.
Let’s build a child component, Product, that will contain a product listing. Just like with the
ProductList component, we’ll use the React.createClass() function with a single key of render:
const Product = React.createClass({
render: function () {
return ()
}
});
For every product, we’ll add an image, a title, a description, and an avatar of the post author. The
relevant code might look something like:
const Product = React.createClass({
render: function () {
return (
Authentic renaissance actors, delivered in just two weeks.
Submitted by:
);
},
});
We’ve used a bit of SemanticUI styling in our code here. As we discussed previously, this JSX code
will be compiled to JavaScript in the browser. As it runs in the browser as JavaScript, we cannot use
Your first React Web Application 17
any reserved JavaScript words in JSX. Setting the class attribute on an HTML element is how we
add a Cascading StyleSheet (CSS, for short) to apply styles to the class. In JSX, however, we cannot
use class, so React changes the key from class to className.
Structurally, the Product component is similar to the ProductList component. Both have a single
render() method which returns information about an eventual HTML structure to display.
Remember, the JSX components return is not actually the HTML that gets rendered, but
is the representation that we want React to render in the DOM. This course looks at this
in-depth in later sections.
To use the Product component, we can modify our parent ProductList component to list the
Product component:
const ProductList = React.createClass({
render: function () {
return (
);
},
});
Save app.js and refresh the web browser.
Your first React Web Application 18
With this update, we now have two React components being rendered in our webapp. The
ProductList parent component is rendering the Product component as a child nested underneath
its root div element.
At the moment, the child Product component is static. We hardcoded an image, the name, the
description, and author details. To use this component in a meaningful way, we’ll change it to be
data-driven and therefore dynamic.
Making Product data-driven
Attributes in Product like title and description are currently hard-coded. We will need to tweak our
Product component to make it data-driven. Having the component be driven by data will allow us
to dynamically render the component based upon the data that we give it. Let’s familiarize ourselves
with the product data model.
The data model
In the sample code, we’ve included a file called data.js which contains some example data for
our products. In the future, we might fetch this data over a network request (we cover this in
later sections). The data.js file contains a JavaScript object called Data that contains an array of
JavaScript objects, each representing a product object:
id: 1,
title: 'Yellow Pail',
description: 'On-demand sand castle construction expertise.',
url: '#',
votes: generateVoteCount(),
submitter_avatar_url: 'images/avatars/daniel.jpg',
product_image_url: 'images/products/image-aqua.png',
},
{
Each product has a unique id and a handful of properties including a title and description. votes
are randomly generated for each one with the included function generateVoteCount().
We can use the same attribute keys in our React code.
Using props
We want to modify our Product component so that it no longer uses static, hard-coded attributes, but
instead is able to accept data passed down from its parent, ProductList. Setting up our component
Your first React Web Application 19
structure in this way enables our ProductList component to dynamically render any number of
Product components that each have their own unique attributes. Data flow will look like this:
The way data flows from parent to child in React is through props. When a parent renders a child,
it can send along props the child can depend upon.
Let’s see this in action. First, let’s modify ProductList to pass down props to Product. Using our
Data object instead of typing the data in, let’s pluck the first object off of the Data array and use that
for the single product:
const ProductList = React.createClass({
render: function () {
const product = Data[0];
return (
);
},
});
Here, the product variable is a JavaScript object that describes the first of our products. We pass
the product’s attributes along individually to the Product component using the syntax [prop_-
name]=[prop_value]. The syntax of assigning attributes in JSX is exactly the same as HTML and
XML.
Your first React Web Application 20
There are two interesting things here. The first is the braces ({}) around each of the property values:
id={product.id}
In JSX, braces are a delimiter, signaling to JSX that what resides in-between the braces is an
expression. To pass in a string instead of an expression, for instance, we can do so like this:
id='1'
Using the ' as a delimiter for the string instead of the {}.
JSX attribute values must be delimited by either braces or quotes.
If type is important and we want to pass in something like a Number or a null, use braces.
Now the ProductList component is passing props down to Product. Our Product component isn’t
using them yet, so let’s modify the component to use these props:
const Product = React.createClass({
render: function () {
return (
);
},
});
In React, we can access all component props through the this.props in a component object. We can
access all of the various props we passed along with the names assigned by ProductList. Again,
we’re using braces as a delimiter.
this is a special keyword in JavaScript. The details about this are a bit nuanced, but for
the purposes of the majority of this book, this will be bound to the React component class
and we’ll discuss when it differs in later sections. We use this to call methods on the
component.
For more details on this, check out this page on MDN¹⁸.
With our updated app.js file saved, let’s refresh the web browser again:
The ProductList component now shows a single product listed, the first object pulled from Data.
Now that Product is rendering itself based on the props that it receives from ProductList, our code
is poised to render any number of unique products.
¹⁸https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
Your first React Web Application 22
Rendering multiple products
Modify the ProductList component again. This time, we’re going to make an array of Product
components, each representing an individual object in the Data array. Rather than passing in a
single product, let’s map over all of our products:
const ProductList = React.createClass({
render: function () {
const products = Data.map((product) => {
return (
);
});
return (
{products}
);
},
});
Before render’s return method, we create a variable called products. We use JavaScript’s map
function to iterate over each one of the objects in the Data array.
The function passed to map simply returns a Product component. Notably, we’re able to represent the
Product component instance in JSX inside of return without issue because it compiles to JavaScript.
This Product is created exactly as before with the same props. As such, the products variable ends
up just being an array of Product components, which in this case is four total.
Your first React Web Application 23
Array’s map method takes a function as an argument. It calls this function with each item
inside of the array (in this case, each object inside Data) and builds a new array by using
the return value from each function call.
Because the Data array has four items, map will call this function four times, once for each
item. When map calls this function, it passes in as the first argument an item. The return
value from this function call is inserted into the new array that map is constructing. After
handling the last item, map returns this new array. Here, we’re storing this new array in
the variable products.
Note the use of the key={'product-' + product.id} prop. React uses this special property
to create unique bindings for each instance of the Product component. The key prop is not
used by our Product component, but by the React framework. It’s a special property that
we discuss deeper in our advanced components section. For the time being, it’s enough to
note that this property needs to be unique per React instance in a map.
ES6: Arrow functions
Up until this point, we’ve been using the traditional JavaScript function declaration syntax:
render: function () { ... }
We’ve been using this syntax to declare object methods for our React component classes.
Inside of the render() method of ProductList, we pass an anonymous arrow function to
map(). Arrow functions were introduced in ES6. Throughout the book, whenever we’re declaring
anonymous functions we will use arrow functions. This is for two reasons.
Your first React Web Application 24
The first is that the syntax is much terser. Compare this declaration:
const timestamps = messages.map(function(m) { return m.timestamp });
To this one:
const timestamps = messages.map(m => m.timestamp);
Of greater benefit, though, is how arrow functions bind the this object. We cover the semantics
of this difference later in the sidebar titled “Arrow functions and this.”
After we save the updates to our app.js and refresh the page, we’ll see that we have five total
React components at work. We have a single parent component, our ProductList component which
contains four child Product components, one for each product object in our Data variable (from
data.js).
Product components (orange) inside of the ProductList component (red)
We added an ‘up vote’ caret icon in our Product component above. If we click on one of these
buttons, we’ll see that nothing happens. We’ve yet to hook up an event to the button.
Although we have a dynamic React app running in our web browser, this page still lacks interactivity.
While React has given us an easy and clean way to organize our HTML thus far and enabled us to
drive HTML generation based on a flexible, dynamic JavaScript object, we’ve still yet to tap into its
true power in enabling super dynamic, interactive interfaces.
The rest of this course digs deep into this power. Let’s start with something simple: the ability to
Your first React Web Application 25
up-vote a given product.
React the vote (your app’s first interaction)
When the up-vote button on each one of the Product components is clicked, we expect it to update
the votes attribute for that Product, increasing it by one. In addition, this vote should be updated
in the Data variable. While Data is just a JavaScript variable right now, in the future it could just as
easily be a remote database. We would need to inform the remote database of the change. While we
cover this process in-depth in later sections of this course, we’ll get used to this practice by updating
Data.
The Product component can’t modify its votes. this.props is immutable.
Remember, while the child has access to read its own props, it doesn’t own them. In our app, the
parent component, ProductList, owns them. React favors the idea of one-way data flow. This means
that data changes come from the “top” of the app and are propagated “downwards” through its
various components.
A child component does not own its props. Parent components own the props of the child
component.
We need a way for the Product component to let ProductList know that a click on its up-vote
event happened and then let ProductList, the owner of both that product’s props as well as the
store (Data), handle the rest. It can update Data and then data will flow downward from Data,
through the ProductList component, and finally to the Product component.
Propagating the event
Fortunately, propagating an event from a child component to a parent is easy. We can pass down
functions as props too. We can have the ProductList component give each Product component a
function to call when the up-vote button is clicked. Functions passed down through props are the
canonical manner in which children communicate events with their parent components.
Let’s start by modifying Product to call a function when the up-vote button is clicked.
First, add a new component function to Product, handleUpVote(). We’ll anticipate the existence of
a new prop called onVote() that’s a function passed down by ProductList:
Your first React Web Application 26
const Product = React.createClass({
handleUpVote: function () {
this.props.onVote(this.props.id);
},
render: function () {
// ...
We’re setting up an expectation that the ProductList component sends a function as a prop to the
Product component. We’ll call this function onVote(). onVote() accepts a single argument. The
argument we’re passing in is the id of the product (this.props.id). The id is sufficient information
for the parent to deduce which Product component has produced this event. We’ll implement the
function shortly.
We can have an HTML element inside the Product component call a function when it is clicked.
We’ll set the onClick attribute on the a HTML tag that is the up-vote button. By passing the name
of the function handleUpVote() to this attribute, it will call our new handleUpVote() function when
the up-vote button is clicked:
const Product = React.createClass({
handleUpVote: function () {
this.props.onVote(this.props.id);
},
render: function () {
return (
// ...
{this.props.votes}
// ...
Let’s define the function in ProductList that we pass down to Product as the prop onVote(). This
is the function that the Product component’s handleUpVote() calls whenever the up-vote button is
clicked:
Your first React Web Application 27
const ProductList = React.createClass({
handleProductUpVote: function (productId) {
console.log(productId + " was upvoted.");
},
render: function () {
const products = Data.map((product) => {
return (
);
});
return (
{products}
);
},
});
First we define a handleProductUpVote() function on ProductList. We’ve set it up to accept a
productId, as anticipated.
This function is then passed down as a prop, onVote, just the same as any other prop.
ES6: Arrow functions and this
Inside ProductList, we use array’s map() method on Data to setup the variable products. We pass
an anonymous arrow function to map(). Inside this arrow function, we call this.handleProductUpVote.
Here, this is bound to the React object.
We introduced arrow functions earlier and mentioned that one of their benefits was how they bind
the this object.
The traditional JavaScript function declaration syntax (function () {}) will bind this in anonymous
functions to the global object. To illustrate the confusion this causes, consider the following
Your first React Web Application 28
example:
function printSong() {
console.log("Oops - The Global Object");
}
const jukebox = {
songs: [
{
title: "Wanna Be Startin' Somethin'",
artist: "Michael Jackson",
},
{
title: "Superstar",
artist: "Madonna",
},
],
printSong: function (song) {
console.log(song.title + " - " + song.artist);
},
printSongs: function () {
// `this` bound to the object (OK)
this.songs.forEach(function (song) {
// `this` bound to global object (bad)
this.printSong(song);
});
},
}
jukebox.printSongs();
// > "Oops - The Global Context"
// > "Oops - The Global Context"
The method printSongs() iterates over this.songs with forEach(). In this context, this is bound
to the object (jukebox) as expected. However, the anonymous function passed to forEach() binds
its internal this to the global object. As such, this.printSong(song) calls the function declared
at the top of the example, not the method on jukebox.
JavaScript developers have traditionally used workarounds for this behavior, but arrow functions
solve the problem by capturing the this value of the enclosing context. Using an arrow function
for printSongs() has the expected result:
Your first React Web Application 29
// ...
printSongs: function () {
this.songs.forEach((song) => {
// `this` bound to same `this` as `printSongs()` (`jukebox`)
this.printSong(song);
});
},
}
jukebox.printSongs();
// > "Wanna Be Startin' Somethin' - Michael Jackson"
// > "Superstar - Madonna"
For this reason, throughout the book we will use arrow functions for all anonymous functions.
Saving our updated app.js, refreshing our web browser, and clicking an up-vote will log some text
to our JavaScript console:
The events are being propagated up to the parent. Finally, we need ProductList to update the store,
Data.
Your first React Web Application 30
It’s tempting to modify the Data JavaScript object directly. We could hunt through Data until we
find the corresponding product and then update its vote count. However, React will have no idea
this change occurred. So while the vote count will be updated in the store, this update will not be
reflected to the user. This would be equivalent to just making a call to a server to update the vote
count but then doing nothing else. The front-end would be none the wiser.
In order to move forward, there’s one more critical concept for React components we need to cover:
state.
Using state
Whereas props are immutable and owned by a component’s parent, state is mutable and owned by
the component. this.state is private to the component and can be updated with this.setState().
As with props, when the state updates the component will re-render itself.
Every React component is rendered as a function of its this.props and this.state. This rendering
is deterministic. This means that given a set of props and a set of state, a React component will always
render a single way. As we mentioned earlier, this approach makes for a powerful UI consistency
guarantee.
As we are mutating the data for our products (the number of votes), we should consider this data
to be stateful. We should treat Data as some external store, which is used to update this.state for
ProductList.
Let’s modify the ProductList component’s render function so that it uses state as opposed to
accessing Data directly. In order to tell React that our component isstateful, we’ll define the function
getInitialState() and return a non-falsey value:
const ProductList = React.createClass({
getInitialState: function () {
return {
products: [],
};
},
handleProductUpVote: function (productId) {
console.log(productId + " was upvoted.");
},
render: function () {
const products = this.state.products.map((product) => {
return (
{
return b.votes - a.votes;
});
this.setState({ products: products });
},
handleProductUpVote: function (productId) {
console.log(productId + " was upvoted.");
},
render: function () {
// ...
In our ProductList component, the componentDidMount() function uses the native JavaScript
Array’s sort() method to ensure that we sort products based upon the number of votes in
descending order. This sorted array of products is then used in the special component method
this.setState(). As anticipated, this method updates this.state and re-renders the component.
As we’ll need to update and sort the state after we click on an up-vote button, we can define this
functionality as a single function so we don’t need to duplicate this functionality. We’ll call this
function updateState():
Your first React Web Application 32
const ProductList = React.createClass({
getInitialState: function () {
return {
products: [],
};
},
componentDidMount: function () {
this.updateState();
},
updateState: function () {
const products = Data.sort((a, b) => {
return b.votes - a.votes;
});
this.setState({ products: products });
},
handleProductUpVote: function (productId) {
console.log(productId + " was upvoted.");
},
render: function () {
// ...
Never modify state outside of this.setState(). This function has important hooks
around state modification that we would be bypassing.
We discuss state management in detail throughout the book.
Array’s sort() method takes an optional function as an argument. If the function is
omitted, it will just sort the array by each item’s Unicode code point value. This is rarely
what a programmer desires. If the function is supplied, elements are sorted according to
the functions return value.
On each iteration, the arguments a and b are two elements in the array. Sorting depends
on the return value of the function:
1. If the return value is less than 0, a should come first (have a lower index).
2. If the return value is greater than 0, b should come first.
3. If the return value is equal to 0, leave order of a and b unchanged with respect to
each other.
sort() mutates the original array it was called on. Later on in the course, we discuss why
mutating arrays or objects can be a dangerous pattern.
Your first React Web Application 33
If we save and refresh now, we see that the products are back.
Updating state
With state management in place, we need to modify handleProductUpVote() inside ProductList.
When handleProductUpVote() is invoked from inside the Product component, it should update
Data and then trigger a state update for ProductList:
const ProductList = React.createClass({
// ...
handleProductUpVote: function (productId) {
Data.forEach((el) => {
if (el.id === productId) {
el.votes = el.votes + 1;
return;
}
});
this.updateState();
},
// ...
We use Array’s forEach() to traverse Data. When a matching object is found (based upon the object’s
id), its votes attribute is incremented by 1. After Data is modified, we’ll call this.updateState()
to update the state to the current products value. The component’s state is then updated and React
intelligently re-renders the UI to reflect these updates.
Array’s forEach() method executes the provided function once for each element present
in the array in ascending order.
Handling these updates on a remote store would be a fairly trivial update. Instead of
modifying Data inside of handleProductUpVote(), we would make a call to a remote
service. The server’s response would trigger a callback that would then make a subsequent
call to the service asking for the most recent data, updating the state with the data from
the response. We’ll be exploring server communication in this course’s next project.
Save app.js, refresh the browser, and cross your fingers:
Your first React Web Application 34
At last, the vote counters are working! Try up-voting a product a bunch of times and notice how it
quickly jumps above products with lower vote counts.
Technically, we can perform the same operation in this.updateState() within
getInitialState() as well, bypassing the brief period where the state is empty. However,
this is usually bad practice for a variety of reasons.
We look into why in our advanced components section, but briefly, our state will be set
by a call to a remote server. As React is still performing the initial render of the app, we’d
have to halt it in its tracks, block on a web request, and then update the state when the
result is returned. Instead, by just giving React a valid blank “scaffold” for the state, React
will render everything first then make parallel, asynchronous calls to whichever servers to
fill in the details. Much more efficient.
Again, the style of this app differs slightly from the demo we saw at the beginning of this
section. If you’d like to add some additional style to your components, refer to the HTML
structure in app-complete.js.
Congratulations!
We have just completed our first React app. Not so bad, eh?
Your first React Web Application 35
There are a ton of powerful features we’ve yet to go over, yet all of them build upon the core
fundamentals we just covered:
1. Think about and organize your app into components
2. JSX and the render method
3. Data flow from parent to children through props
4. Event flow from children to parent through functions
5. State vs props
6. How to manipulate state
7. Utilizing React lifecycle methods
Onward!
Chapter Exercises
1. Add down-voting capability to each Product. You can insert a down arrow with this
JSX snippet:
2. Add a “sort direction” button to the top of ProductList, above all the products. It
should enable the user to toggle sorting products by ascending or descending.
Components
A time-logging app
In the last chapter, we described how React organizes apps into components and how data flows
between parent and child components. And we discussed core concepts such as how we manage
state and pass data between components using props.
In this chapter, we construct a more intricate application. We investigate a pattern that we will use
to build all React apps and then put those steps to work to build an interface for managing timers.
In this time-tracking app, a user can add, delete, and modify various timers. Each timer corresponds
to a different task that the user would like to keep time for:
This app will have significantly more interactive capabilities than the one built in the last chapter.
This will present us with some interesting challenges that will deepen our familiarity with React’s
core concepts.
Components 37
Getting started
Sample Code
As with all chapters, before beginning make sure you’ve downloaded the book’s sample
code and have it at the ready.
Previewing the app
Let’s begin by playing around with a completed implementation of the app.
In your terminal, cd into the time_tracking_app directory:
$ cd time_tracking_app
Use npm to install all the dependencies:
$ npm install
Then boot the server:
$ npm run server
Now you can view the app in your browser. Open your browser and enter the URL http://localhost:3000¹⁹.
Play around with it for a few minutes to get a feel for all the functionality. Refresh and note that
your changes have been persisted.
Prepare the app
In your terminal, run ls -1p to see the project’s layout:
$ ls -1p
¹⁹http://localhost:3000
Components 38
README.md
data.json
node_modules/
package.json
public/
server.js
There are a few organizational changes from the last project.
First, notice that there is now a server.js in this project. In the last chapter, we used a pre-built
Node package (called http-server) to serve our assets.
This time we have a custom-built server which serves our assets and also adds a persistence layer.
We will cover the server in detail in the next chapter.
When you visit a website, assets are the files that your browser downloads and uses to
display the page. index.html is delivered to the browser and inside its head tags it specifies
which additional files from the server the browser needs to download.
In the last project, our assets were index.html as well as our stylesheets and images.
In this project, everything under public/ is an asset.
In the voting app, we loaded all of our app’s initial data from a JavaScript variable, loaded in the file
data.js.
This time, we’re going to eventually store it in the text file data.json. This brings the behavior a bit
closer to a database. By using a JSON file, we can make edits to our data that will be persisted even
if the app is closed.
JSON stands for JavaScript Object Notation. JSON enables us to serialize a JavaScript object
and read/write it from a text file.
If you’re not familiar with JSON, take a look at data.json. Pretty recognizeable, right?
JavaScript has a built-in mechanism to parse the contents of this file and initialize a
JavaScript object with its data.
Peek inside public:
$ cd public
$ ls -1p
And you will find some familiar files:
Components 39
vendor/
app-complete.js
app.js
client.js
helpers.js
index.html
style.css
Again, we’ll be editing only the files index.html and app.js. index.html is the actual webpage
which loads our React app which is written inside of app.js. helpers.js and client.js contain
some pre-written JavaScript functions that we will use in our app.
As before, our first step is to ensure app-complete.js is no longer loaded in index.html. We’ll
instead load the empty file app.js.
Open up index.html. It looks like this:
Project Two: Timers
Timers
Components 40
Overall, this file is very similar to the one we used in our voting app. We load in our dependencies
within the head tags (the assets). Inside of body we have a few elements. This div is where we will
ultimately mount our React app:
And this script tag is where we instruct the browser to load app.js into the page:
Do as the comment says and delete the line that loads app-complete.js:
Save index.html. If you reload the page now, you’ll see the app has disappeared.
Breaking the app into components
As we did with our last project, we begin by breaking our app down into its components. Again,
visual components often map tightly to their respective React components. Let’s examine the
interface of our app:
Components 41
In the last project, we had ProductList and Product components. The first contained instances of
the second. Here, we spot the same pattern, this time with TimerList and Timer components:
Components 42
However, there’s one subtle difference: This list of timers has a little “+” icon at the bottom. As
we saw, we’re able to add new timers to the list using this button. So, in reality, the TimerList
component isn’t just a list of timers. It also contains a widget to create new timers.
Think about components as you would functions or objects. The single responsibility principle²⁰
applies. A component should, ideally, only be responsible for one piece of functionality. So, the
proper response here is for us to shrink TimerList back into its responsibility of just listing timers
and to nest it under a parent component. We’ll call the parent TimersDashboard. TimersDashboard
will have TimerList and the “+”/create form widget as children:
²⁰https://en.wikipedia.org/wiki/Single_responsibility_principle
Components 43
Not only does this separation of responsibilities keep components simple, but it often also improves
their re-usability. In the future, we can now drop the TimerList component anywhere in the app
where we just want to display a list of timers. This component no longer carries the responsibility
of also creating timers, which might be a behavior we want to have for just this dashboard view.
How you name your components is indeed up to you, but having some consistent rules
around language as we do here will greatly improve code clarity.
In this case, developers can quickly reason that any component they come across that ends
in the word List simply renders a list of children and no more.
The “+”/create form widget is interesting because it has two distinct representations. When the “+”
button is clicked, the widget transmutes into a form. When the form is closed, the widget transmutes
back into a “+” button.
There are two approaches we could take. The first one is to have the parent component, TimersDashboard,
decide whether or not to render a “+” component or a form component based on
some piece of stateful data. It could swap between the two children. However, this adds more
responsibility to TimersDashboard. The alternative is to have a new child component own the single
responsibility of determining whether or not to display a “+” button or a create timer form. We’ll
call it ToggleableTimerForm. As a child, it can either render the component TimerForm or the HTML
markup for the “+” button.
At this point, we’ve carved out four components:
Components 44
Now that we have a sharp eye for identifying overburdened components, another candidate should
catch our eye:
A single timer: Displaying time (left) vs. edit form (right)
The timer itself has a fair bit of functionality. It can transform into an edit form, delete itself, and
start and stop itself. Do we need to break this up? And if so, how?
Displaying a timer and editing a timer are indeed two distinct UI elements. They should be two
distinct React components. Like ToggleableTimerForm, we need some container component that
renders either the timer’s face or its edit form depending on if the timer is being edited.
Components 45
We’ll call this EditableTimer. The child of EditableTimer will then be either a Timer component
or the edit form component. The form for creating and editing timers is very similar, so let’s assume
that we can use the component TimerForm in both contexts:
As for the other functionality of the timer, like the start and stop buttons, it’s a bit tough to determine
at this point whether or not they should be their own components. We can trust that the answers
will be more apparent after we’ve written some code.
Working back up the component tree, we can see that the name TimerList would be a misnomer.
It really is a EditableTimerList. Everything else looks good.
So, we have our final component hierarchy, with some ambiguity around the final state of the timer
component:
Components 46
• TimersDashboard (green): Parent container
– EditableTimerList (red): Displays a list of timer containers
* EditableTimer (aqua): Displays either a timer or a timer’s edit form
· Timer (yellow): Displays a given timer
· TimerForm (blue): Displays a given timer’s edit form
– ToggleableTimerForm (purple): Displays a form to create a new timer
* TimerForm (not displayed): Displays a new timer’s create form
Represented as a hierarchical tree:
Components 47
In our previous app, ProductList handled not only rendering components, but also the
responsibility of handling up-votes and talking to the store. While this worked for that
app, you can imagine that as a codebase expands, there may come a day where we’d want
to free ProductList of this responsibility.
For example, imagine if we added a “sort by votes” feature to ProductList. What if we
wanted some pages to be sortable (category pages) but other pages to be static (top 10)?
We’d want to “hoist” sort responsibility up to a parent component and make ProductList
the straightforward list renderer that it should be.
This new parent component could then include the sorting-widget component and then
pass down the ordered products to the ProductList component.
The steps for building React apps
Now that we have a good understanding of the composition of our components, we’re ready to build
a static version of our app. Ultimately, our top-level component will communicate with a server. The
Components 48
server will be the initial source of state, and React will render itself according to the data the server
provides. Our app will also send updates to the server, like when a timer is started:
However, it will simplify things for us if we start off with static components, as we did in the last
chapter. Our React components will do little more than render HTML. Clicking on buttons won’t
yield any behavior as we will not have wired up any interactivity. This will enable us to lay the
framework for the app, getting a clear idea of how the component tree is organized.
Next, we can determine what the state should be for the app and in which component it should
live. We’ll start off by just hard-coding the state into the components instead of loading it from the
server.
At that point, we’ll have the data flow from parent to child in place. Then we can add inverse data
flow, propagating events from child to parent.
Finally, we’ll modify the top-level component to have it communicate with the server.
In fact, the development of each React app will follow this pattern:
1. Break the app into components
2. Build a static version of the app
3. Determine what should be stateful
4. Determine in which component each piece of state should live
5. Hard-code initial states
6. Add inverse data flow
Components 49
7. Add server communication
We actually followed this pattern in the last project:
1. Break the app into components
We looked at the desired UI and determined we wanted ProductList and Product components.
2. Build a static version of the app
Our components started off without using state. Instead, we had ProductList pass down static
props to Product.
3. Determine what should be stateful
In order for our application to become interactive, we had to be able to modify the vote property on
each product. Each product had to be mutable and therefore stateful.
4. Determine in which component each piece of state should live
ProductList managed the voting state using React component class methods.
5. Hard-code initial state
When we re-wrote ProductList to use this.state, we had it use the hard-coded variable Data.
6. Add inverse data flow
We defined the handleUpVote function in ProductList and passed it down in props so that each
Product could inform ProductList of up-vote events.
7. Add server communication
We did not add a server component to our last app, but we will be doing so in this one.
If steps in this process aren’t completely clear right now, don’t worry. The purpose of this chapter is
to familiarize yourself with this procedure.
We’ve already covered step (1) and have a good understanding of all of our components, save for
some uncertainty down at the Timer component. Step (2) is to build a static version of the app. As
in the last project, this amounts to defining React components, their hierarchy, and their HTML
representation. We completely avoid state for now.
Step 2: Build a static version of the app
TimersDashboard
Let’s start off with the TimersDashboard component. Again, all of our React code for this chapter
will be inside of the file public/app.js.
We’ll begin by defining a familiar function, render():
Components 50
const TimersDashboard = React.createClass({
render: function () {
return (
);
},
});
This component renders its two child components nested under div tags. TimersDashboard passes
down one prop to ToggleableTimerForm: isOpen. This is used by the child component to determine
whether to render a “+” or TimerForm. When ToggleableTimerForm is “open” the form is being
displayed.
As in the last chapter, don’t worry about the className attribute on the div tags. This will
ultimately define the class on HTML div elements and is purely for styling purposes.
In this example, classes like ui three column centered grid all come from the CSS
framework Semantic UI²¹. The framework is included in the head of index.html.
We will define EditableTimerList next. We’ll have it render two EditableTimer components. One
will end up rendering a timer’s face. The other will render a timer’s edit form:
const EditableTimerList = React.createClass({
render: function () {
return (
);
},
});
We’re passing five props to each child component. The key difference between the two EditableTimer
components is the value being set for editFormOpen. We’ll use this boolean to instruct
EditableTimer which sub-component to render.
The purpose of the prop runningSince will be covered later on in the app’s development.
EditableTimer
EditableTimer returns either a TimerForm or a Timer based on the prop editFormOpen:
const EditableTimer = React.createClass({
render: function () {
if (this.props.editFormOpen) {
return (
);
} else {
return (
);
Components 52
}
},
});
Note that title and project are passed down as props to TimerForm. This will enable the component
to fill in these fields with the timer’s current values.
TimerForm
We’ll build an HTML form that will have two input fields. The first input field is for the title and
the second is for the project. It also has a pair of buttons at the bottom:
const TimerForm = React.createClass({
render: function () {
const submitText = this.props.title ? 'Update' : 'Create';
return (
);
},
});
Components 53
Look at the input tags. We’re specifying that they have type of text and then we are using the
React property defaultValue. When the form is used for editing as it is here, this sets the fields to
the current values of the timer as desired.
Later, we’ll use TimerForm again within ToggleableTimerForm for creating timers.
ToggleableTimerForm will not pass TimerForm any props. this.props.title and
this.props.project will therefore return undefined and the fields will be left empty.
At the beginning of render(), before the return statement, we define a variable submitText. This
variable uses the presence of this.props.title to determine what text the submit button at the
bottom of the form should display. If title is present, we know we’re editing an existing timer, so
it displays “Update.” Otherwise, it displays “Create.”
With all of this logic in place, TimerForm is prepared to render a form for creating a new timer or
editing an existing one.
We used an expression with the ternary operator to set the value of submitText. The
syntax is:
1 condition ? expression1 : expression2
If the condition is true, the operator returns the value of expression1. Otherwise, it
returns the value of expression2. In our example, the variable submitText is set to the
returned expression.
ToggleableTimerForm
Let’s turn our attention next to ToggleableTimerForm. Recall that this is a wrapper component
around TimerForm. It will display either a “+” or a TimerForm. Right now, it accepts a single prop,
isOpen, from its parent that instructs its behavior:
const ToggleableTimerForm = React.createClass({
render: function () {
if (this.props.isOpen) {
return (
);
} else {
return (
);
}
},
});
As noted earlier, TimerForm does not receive any props from ToggleableTimerForm. As such, its
title and project fields will be rendered empty.
The return statement under the else block is the markup to render a “+” button. You could make
a case that this should be its own React component (say PlusButton) but at present we’ll keep the
code inside ToggleableTimerForm.
Timer
Time for the Timer component. Again, don’t worry about all the div and span elements and
className attributes. We’ve provided these for styling purposes:
const Timer = React.createClass({
render: function () {
const elapsedString = helpers.renderElapsedString(this.props.elapsed);
return (
{this.props.title}
{this.props.project}
{elapsedString}
Components 55
Start
);
},
});
elapsed in this app is in milliseconds. This is the representation of the data that React will keep.
This is a good representation for machines, but we want to show our carbon-based users a more
readable format.
We use a function defined in helpers.js, renderElapsedString(). You can pop open that file if
you’re curious about how it’s implemented. The string it renders is in the format ‘HH:MM:SS’.
Note that we could store elapsed in seconds as opposed to milliseconds, but JavaScript’s
time functionality is all in milliseconds. We keep elapsed consistent with this for simplicity.
As a bonus, our timers are also slightly more accurate, even though they round to seconds
when displayed to the user.
Render the app
With all of the components defined, the last step before we can view our static app is to ensure we
call ReactDOM#render(). Put this at the bottom of the file:
ReactDOM.render(
,
document.getElementById('content')
);
Again, we specify with ReactDOM#render() which React component we want to render
and where in our HTML document (index.html) to render it.
In this case, we’re rendering TimersDashboard at the div with the id of content.
Try it out
Save app.js and boot the server (npm run server). Find it at localhost:3000²²:
²²localhost:3000
Components 56
Tweak some of the props and refresh to see the results. For example:
• Flip the prop passed down to ToggleableTimerForm from true to false and see the “+” button
render.
• Flip parameters on editFormOpen and witness EditableTimer flip the child it renders
accordingly.
Let’s review all of the components represented on the page:
Inside TimersDashboard are two child components: EditableTimerList and ToggleableTimerForm.
EditableTimerList contains two EditableTimer components. The first of these has a Timer
component as a child and the second a TimerForm. These bottom-level components — also known
as leaf components — hold the majority of the page’s HTML. This is generally the case. The
components above leaf components are primarily concerned with orchestration.
ToggleableTimerForm renders a TimerForm. Notice how the two forms on the page have different
language for their buttons, as the first is updating and the second is creating.
Components 57
Step 3: Determine what should be stateful
In order to bestow our app with interactivity, we must evolve it from its static existence to a mutable
one. The first step is determining what, exactly, should be mutable. Let’s start by collecting all of the
data that’s employed by each component in our static app. In our static app, data will be wherever
we are defining or using props. We will then determine which of that data should be stateful.
TimersDashboard
In our static app, this declares two child components. It sets one prop, which is the isOpen boolean
that is passed down to ToggleableTimerForm.
EditableTimerList
This declares two child components, each which have props corresponding to a given timer’s
properties.
EditableTimer
This uses the prop editFormOpen.
Timer
This uses all the props for a timer.
TimerForm
This uses the title and project props when editing an existing timer.
State criteria
We can apply criteria to determine if data should be stateful:
These questions are from the excellent article by Facebook called “Thinking In React”. You
can read the original article here²³.
1. Is it passed in from a parent via props? If so, it probably isn’t state.
A lot of the data used in our child components are already listed in their parents. This criterion helps
us de-duplicate.
For example, “timer properties” is listed multiple times. When we see the properties declared in
EditableTimerList, we can consider it state. But when we see it elsewhere, it’s not.
2. Does it change over time? If not, it probably isn’t state.
This is a key criterion of stateful data: it changes.
²³https://facebook.github.io/react/docs/thinking-in-react.html
Components 58
3. Can you compute it based on any other state or props in your component? If so, it’s not
state.
For simplicity, we want to strive to represent state with as few data points as possible.
Applying the criteria
TimersDashboard
• isOpen boolean for ToggleableTimerForm
Stateful. The data is defined here. It changes over time. And it cannot be computed from other state
or props.
EditableTimerList
• Timer properties
Stateful. The data is defined in this component, changes over time, and cannot be computed from
other state or props.
EditableTimer
• editFormOpen for a given timer
Stateful. The data is defined in this component, changes over time, and cannot be computed from
other state or props.
Timer
• Timer properties
In this context, not stateful. Properties are passed down from the parent.
TimerForm
• title and project properties for a given timer
Not stateful. Properties are passed down from parent.
We’ve identified our stateful data:
• The list of timers and properties of each timer
• Whether or not the edit form of a timer is open
• Whether or not the create form is open
Components 59
Step 4: Determine in which component each piece of
state should live
While the data we’ve determined to be stateful might live in certain components in our static app,
this does not indicate the best position for it in our stateful app. Our next task is to determine the
optimal place for each of our three discrete pieces of state to live.
This can be challenging at times but, again, we can apply the following steps from Facebook’s guide
“Thinking in React²⁴” to help us with the process:
For each piece of state:
• Identify every component that renders something based on that state.
• Find a common owner component (a single component above all the components
that need the state in the hierarchy).
• Either the common owner or another component higher up in the hierarchy should
own the state.
• If you can’t find a component where it makes sense to own the state, create a new
component simply for holding the state and add it somewhere in the hierarchy
above the common owner component.
Let’s apply this method to our application:
The list of timers and properties of each timer
At first glance, we may be tempted to conclude that TimersDashboard does not appear to use this
state. Instead, the first component that uses it is EditableTimerList. This matches the location of
the declaration of this data in our static app. Because ToggleableTimerForm doesn’t appear to use
the state either, we might deduce that EditableTimerList must then be the common owner.
While this may be the case for displaying timers, modifying them, and deleting them, what about
creates? ToggleableTimerForm does not need the state to render, but it can affect state. It needs to
be able to insert a new timer. It will propagate the data for the new timer up to TimersDashboard.
Therefore, TimersDashboard is truly the common owner. It will render EditableTimerList by
passing down the timer state. It can the handle modifications from EditableTimerList and
creates from ToggleableTimerForm, mutating the state. The new state will flow downward through
EditableTimerList.
²⁴https://facebook.github.io/react/docs/thinking-in-react.html
Components 60
Whether or not the edit form of a timer is open
In our static app, EditableTimerList specifies whether or not a EditableTimer should be rendered
with its edit form open. Technically, though, this state could just live in each individual EditableTimer.
No parent component in the hierarchy depends on this data.
Storing the state in EditableTimer will be fine for our current needs. But there are a few
requirements that might require us to “hoist” this state up higher in the component hierarchy in
the future.
For instance, what if we wanted to impose a restriction such that only one edit form could be
open at a time? Then it would make sense for EditableTimerList to own the state, as it would
need to inspect it to determine whether to allow a new “edit form open” event to succeed. If we
wanted to allow only one form open at all, including the create form, then we’d hoist the state up
to TimersDashboard.
Visibility of the create form
TimersDashboard doesn’t appear to care about whether ToggleableTimerForm is open or closed. It
feels safe to reason that the state can just live inside ToggleableTimerForm itself.
So, in summary, we’ll have three pieces of state each in three different components:
• Timer data will be owned and managed by TimersDashboard.
• Each EditableTimer will manage the state of its timer edit form.
• The ToggleableTimerForm will manage the state of its form visibility.
Step 5: Hard-code initial states
We’re now well prepared to make our app stateful. At this stage, we won’t yet communicate with
the server. Instead, we’ll define our initial states within the components themselves. This means
hard-coding a list of timers in the top-level component, TimersDashboard. For our two other pieces
of state, we’ll have the components’ forms closed by default.
After we’ve added initial state to a parent component, we’ll make sure our props are properly
established in its children.
Adding state to TimersDashboard
Start by modifying TimersDashboard to hold the timer data directly inside the component:
Components 61
const TimersDashboard = React.createClass({
getInitialState: function () {
return {
timers: [
{
title: 'Practice squat',
project: 'Gym Chores',
id: uuid.v4(),
elapsed: 5456099,
runningSince: Date.now(),
},
{
title: 'Bake squash',
project: 'Kitchen Chores',
id: uuid.v4(),
elapsed: 1273998,
runningSince: null,
},
],
};
},
render: function () {
return (
);
},
});
In getInitialState, we set the state to an object with the key timers. timers points to an array
with two timer objects.
For the id property, we’re using a library called uuid. We load this library in index.html. We use
uuid.v4() to randomly generate a Universally Unique IDentifier²⁵ for each item.
²⁵https://en.wikipedia.org/wiki/Universally_unique_identifier
Components 62
A UUID is a string that looks like this:
2030efbd-a32f-4fcc-8637-7c410896b3e3
Receiving props in EditableTimerList
EditableTimerList receives the list of timers as a prop, timers. Modify that component to use those
props:
const EditableTimerList = React.createClass({
render: function () {
const timers = this.props.timers.map((timer) => (
));
return (
{timers}
);
},
});
Hopefully this looks familiar. We’re using map on the timers array to build a list of EditableTimer
components. This is exactly how we built our list of Product components inside ProductList in the
last chapter.
We pass the id down to EditableTimer as well. This is a bit of eager preparation. Remember how
Product communicated up to ProductList by calling a function and passing in its id? It’s safe to
assume we’ll be doing this again.
Props vs. state
With your renewed understanding of React’s state paradigm, let’s reflect on props again.
Components 63
Remember, props are state’s immutable accomplice. What existed as mutable state in TimersDashboard
is passed down as immutable props to EditableTimerList.
We talked at length about what qualifies as state and where state should live. Mercifully, we do not
need to have an equally lengthy discussion about props. Once you understand state, you can see
how props act as its one-way data pipeline. State is managed in some select parent components
and then that data flows down through children as props.
If state is updated, the component managing that state re-renders by calling render(). This, in turn,
causes any of its children to re-render as well. And the children of those children. And on and on
down the chain.
Let’s continue our own march down the chain.
Adding state to EditableTimer
In the static version of our app, EditableTimer relied on editFormOpen as a prop to be passed down
from the parent. We decided that this state could actually live here in the component itself.
Again, we use getInitialState to setup state for the component. We set the initial value of
editFormOpen to false, which means that the form starts off as closed. We’ll also pass the id property
down the chain:
const EditableTimer = React.createClass({
getInitialState: function () {
return {
editFormOpen: false,
};
},
render: function () {
if (this.state.editFormOpen) {
return (
);
} else {
return (
);
}
},
});
Timer and TimerForm remain stateless
If you look at Timer and TimerForm, you’ll see that they do not need to be modified. They’ve always
been using exclusively props and are so far unaffected by our refactor.
Adding state to ToggleableTimerForm
We know that we’ll need to tweak ToggleableTimerForm as we’ve assigned it some stateful
responsibility. While we’re here, we can also add a little bit of interactivity:
const ToggleableTimerForm = React.createClass({
getInitialState: function () {
return {
isOpen: false,
};
},
handleFormOpen: function () {
this.setState({ isOpen: true });
},
render: function () {
if (this.state.isOpen) {
return (
);
} else {
return (
Components 65
);
}
},
});
However small, our app’s first bit of interactivity in place. Like the up-vote button in the last app,
we use the onClick property on button to invoke a function, handleFormOpen(). handleFormOpen()
modifies the state, setting isOpen to true. This causes the component to re-render. When render()
is called this second time around, this.state.isOpen is true and ToggleableTimerForm renders
TimerForm. Neat.
We’ve established our stateful data inside our elected components. Our downward data pipeline,
props, is assembled.
We’re ready — and perhaps a bit eager — to build out interactivity using inverse data flow. But
before we do, let’s save and reload the app to ensure everything is working. We expect to see new
example timers based on the hard-coded data in TimersDashboard. We also expect clicking the “+”
button toggles open a form:
Step 6: Add inverse data flow
As we saw in the last chapter, children communicate with parents by calling functions that are
handed to them via props. In the ProductHunt app, when an up-vote was clicked Product didn’t do
any data management. It was not the owner of its state. Instead, it called a function given to it by
ProductList, passing in its id. ProductList was then able to manage state accordingly.
We are going to need inverse data flow in two areas:
Components 66
• TimerForm needs to propagate create and update events (create while under ToggleableTimerForm
and update while under EditableTimer). Both events will eventually reach
TimersDashboard.
• Timer has a fair amount of behavior. It needs to handle delete and edit clicks, as well as the
start and stop timer logic.
Let’s start with TimerForm.
TimerForm
To get a clear idea of what exactly TimerForm will require, we’ll start by adding event handlers to it
and then work our way backwards up the hierarchy.
TimerForm needs two event handlers:
• When the form is submitted (creating or updating a timer)
• When the “Cancel” button is clicked (closing the form)
TimerForm will receive two functions as props to handle each event. The parent component that uses
TimerForm is responsible for providing these functions:
• props.onFormSubmit(): called when the form is submitted
• props.onFormClose(): called when the “Cancel” button is clicked
As we’ll see soon, this empowers the parent component to dictate what the behavior should be when
these events occur.
Implement these event handlers now:
const TimerForm = React.createClass({
handleSubmit: function () {
this.props.onFormSubmit({
id: this.props.id,
title: this.refs.title.value,
project: this.refs.project.value,
});
},
render: function () {
const submitText = this.props.id ? 'Update' : 'Create';
return (
Components 67
);
},
});
Starting with the form itself, notice how we again capture click events using onClick, an attribute
on both button elements. We also made one modification to both input elements. We added the
attributes ref='title' and ref='project'. Looking at handleSubmit(), you can see how we use
this.refs to access the values of these fields.
handleSubmit() calls a yet-to-be-defined function, onFormSubmit(). It passes in a data object with
id, title, and project attributes, extracting values from this.refs for title and project. id is
pulled from this.props. This means id will be undefined for creates, as no id exists yet.
For the cancel button, we just pass in the prop-function onFormClose() directly. We do not need to
Components 68
supply this function with any arguments so there’s no need to define an additional handler function
as we did with submits.
Note that we also tweaked submitText so that is uses this.props.id to determine if the
submission will be a create or update. Using the id property to determine whether or not
an object has been created is a more common practice.
ToggleableTimerForm
Let’s chase this event as it bubbles up the component hierarchy. First, we’ll modify ToggleableTimerForm.
We need it to pass down two prop-functions to TimerForm, onFormClose() and
onFormSubmit():
// Inside ToggleableTimerForm
handleFormOpen: function () {
this.setState({ isOpen: true });
},
handleFormClose: function () {
this.setState({ isOpen: false });
},
handleFormSubmit: function (timer) {
this.props.onFormSubmit(timer);
this.setState({ isOpen: false });
},
render: function () {
if (this.state.isOpen) {
return (
);
} else {
Looking first at the render() function, we can see we pass in the two functions as props. Functions
are just like any other prop.
Of most interest here is handleFormSubmit(). Remember, ToggleableTimerForm is not the manager
of timer state. TimerForm has an event it’s emitting, in this case the submission of a new timer.
ToggleableTimerForm is just a proxy of this message. So, when the form is submitted, it calls its own
prop-function props.onFormSubmit(). We’ll eventually define this function in TimersDashboard.
Components 69
handleFormSubmit() accepts the argument timer. Recall that in TimerForm this argument is an
object containing the desired timer properties. We just pass that argument along here.
After invoking onFormSubmit(), handleFormSubmit() calls setState() to close its form.
Note that the result of onFormSubmit() will not impact whether or not the form is closed.
We invoke onFormSubmit(), which may eventually create an asynchronous call to a server.
Execution will continue before we hear back from the server which means setState() will
be called.
If onFormSubmit() fails — such as if the server is temporarily unreachable — we’d
ideally have some way to display an error message and re-open the form. We leave the
implementation of this improvement as an exercise at the end of the chapter.
TimersDashboard
We’ve reached the top of the hierarchy, TimersDashboard. As this component will be responsible for
the data for the timers, it is here that we will define the logic for handling the events we’re capturing
down at the leaf components.
The first event we’re concerned with is the submission of a form. When this happens, either a new
timer is being created or an existing one is being updated. We’ll use two separate functions to handle
the two distinct events:
• handleCreateFormSubmit() will handle creates and will be the function passed to ToggleableTimerForm
• handleEditFormSubmit() will handle updates and will be the function passed to EditableTimerList
Both functions travel down their respective component hierarchies until they reach TimerForm as
the prop onFormSubmit().
Let’s start with handleCreateFormSubmit, which inserts a new timer into our timer list state:
Components 70
// Inside TimersDashboard
handleCreateFormSubmit: function (timer) {
this.createTimer(timer);
},
createTimer: function (timer) {
const t = helpers.newTimer(timer);
this.setState({
timers: this.state.timers.concat(t),
});
},
render: function () {
return (
);
},
We create the timer object with helpers.newTimer(). You can peek at the implementation inside
of helpers.js. We pass in the object that originated down in TimerForm. This object has title and
project properties. helpers.newTimer() returns an object with those title and project properties
as well as a generated id.
The next line calls setState(), appending the new timer to our array of timers held under timers.
We pass the whole state object to setState().
concat() creates a new array that contains the elements of the array it was called on
followed by the elements passed in as arguments. If an array is passed in as an argument,
its elements are appended to the new array. For example:
> [ 1, 2, 3 ].concat([ 4, 5 ]);
=> [ 1, 2, 3, 4, 5 ]
Components 71
You might wonder: why separate handleCreateFormSubmit() and createTimer()? While
not strictly required, the idea here is that we have one function for handling the event
(handleCreateFormSubmit()) and another for performing the operation of creating a timer
(createTimer()).
This separation follows from the Single Responsibility Principle and enables us to call
createTimer() from elsewhere if needed.
We’ve finished wiring up the create timer flow from the form down in TimerForm up to the state
managed in TimersDashboard. Save app.js and reload your browser. Toggle open the create form
and create some new timers:
Updating timers
We need to give the same treatment to the update timer flow. However, as you can see in the current
state of the app, we haven’t yet added the ability for a timer to be edited. So we don’t have a way
to display an edit form, which will be a prerequisite to submitting one.
To display an edit form, the user clicks on the edit icon on a Timer. This should propagate an event
up to EditableTimer and tell it to flip its child component, opening the form.
Components 72
Adding editability to Timer
To notify our app that the user wants to edit a timer we need to add an onClick attribute to the span
tag of the edit button. We anticipate a prop-function, onEditClick():
{/* Inside Timer.render() */}
Updating EditableTimer
Now we’re prepared to update EditableTimer. Again, it will display either the TimerForm (if we’re
editing) or an individual Timer (if we’re not editing).
Let’s add event handlers for both possible child components. For TimerForm, we want to handle the
form being closed or submitted. For Timer, we want to handle the edit icon being pressed:
// Inside EditableTimer
handleEditClick: function () {
this.openForm();
},
handleFormClose: function () {
this.closeForm();
},
handleSubmit: function (timer) {
this.props.onFormSubmit(timer);
this.closeForm();
},
closeForm: function () {
this.setState({ editFormOpen: false });
},
openForm: function () {
this.setState({ editFormOpen: true });
},
render: function () {
Components 73
We pass these event handlers down as props:
render: function () {
if (this.state.editFormOpen) {
return (
);
} else {
return (
);
}
},
Look a bit familiar? EditableTimer handles the same events emitted from TimerForm in a very
similar manner as ToggleableTimerForm. This makes sense. Both EditableTimer and ToggleableTimerForm
are just intermediaries between TimerForm and TimersDashboard. TimersDashboard is
the one that defines the submit function handlers and assigns them to a given component tree.
Like ToggleableTimerForm, EditableTimer doesn’t do anything with the incoming timer. In
handleSubmit(), it just blindly passes this object along to its prop-function onFormSubmit(). It then
closes the form with closeForm().
We pass along a new prop to Timer, onEditClick. The behavior for this function is defined in
handleEditClick, which modifies the state for EditableTimer, opening the form.
Updating EditableTimerList
Moving up a level, we make a one-line addition to EditableTimerList to send the submit function
from TimersDashboard to each EditableTimer:
Components 74
// Inside EditableTimerList
const timers = this.props.timers.map((timer) => (
));
// ...
EditableTimerList doesn’t need to do anything with this event so again we just pass the function
on directly.
Defining onEditFormSubmit() in TimersDashboard
Last step with this pipeline is to define and pass down the submit function for edit forms in
TimersDashboard.
For creates, we have a function that creates a new timer object with the specified attributes and we
append this new object to the end of the timers array in the state.
For updates, we need to hunt through the timers array until we find the timer object that is being
updated. As mentioned in the last chapter, the state object cannot be updated directly. We have to
use setState().
Therefore, we’ll use map() to traverse the array of timer objects. If the timer’s id matches that of
the form submitted, we’ll return a new object that contains the timer with the updated attributes.
Otherwise we’ll just return the original timer. This new array of timer objects will be passed to
setState():
// Inside TimersDashboard
handleEditFormSubmit: function (attrs) {
this.updateTimer(attrs);
},
createTimer: function (timer) {
const t = helpers.newTimer(timer);
this.setState({
timers: this.state.timers.concat(t),
});
},
Components 75
updateTimer: function (attrs) {
this.setState({
timers: this.state.timers.map((timer) => {
if (timer.id === attrs.id) {
return Object.assign({}, timer, {
title: attrs.title,
project: attrs.project,
});
} else {
return timer;
}
}),
});
},
render: function () {
We pass this down as a prop inside render():
{ /* Inside TimersDashboard.render() */}
Note that we can call map() on this.state.timers from within the JavaScript object we’re passing
to setState(). This is an often used pattern. The call is evaluated and then the property timers is
set to the result.
Inside of the map() function we check if the timer matches the one being updated. If not, we
just return the timer. Otherwise, we use Object#assign() to return a new object with the timer’s
updated attributes.
As we did with ToggleableTimerForm and handleCreateFormSubmit, we pass down handleEditFormSubmit
as the prop onFormSubmit. TimerForm calls this prop, oblivious to the fact that
this function is entirely different when it is rendered underneath EditableTimer as opposed to
ToggleableTimerForm.
ES6: Object#assign
We will use Object#assign() frequently throughout this book to create new objects as opposed to
modifying existing ones.
Object#assign() accepts any number of objects as arguments. When the function receives two
arguments, it copies the properties of the second object onto the first, like so:
Components 76
const coffee = { };
const noCream = { cream: false };
const noMilk = { milk: false };
Object.assign(coffee, noCream);
// coffee is now: `{ cream: false }`
Object.assign(coffee, noMilk);
// coffee is now: `{ cream: false, milk: false }`
Throughout this book, we’ll often pass three arguments to Object.assign(), as seen in TimersDashboard.
The first argument is a new JavaScript object, the one that Object#assign() will ultimately return.
The second is the object whose properties we’d like to build off of. The last is the changes we’d like
to apply:
const coffeeWithMilk = Object.assign({}, coffee, { milk: true });
// coffeeWithMilk is: `{ cream: false, milk: true }`
// coffee was not modified: `{ cream: false, milk: false }`
Modifying JavaScript objects
Note that we could have gotten away with updating the timer object directly, like so:
// ...
if (timer.id === attrs.id) {
timer.title = attrs.title;
timer.project = attrs.project;
return timer;
}
In JavaScript objects are passed by reference. As such, the code above would have modified the
timer object sitting inside this.state.timers, not a copy of that object. We still call setState(),
though, so React would be notified about this change. It is unlikely negative consequences would
have emerged.
However, manipulating objects in this manner is generally bad practice and something we will
avoid throughout this book. The general rule we will adhere to: unless a function owns an object
(it declared the object), it should refrain from making any edits to it.
To understand why this practice is dangerous, let’s reflect on what could happen in this specific
instance. Consider how many different components in the app reference the state object. Every
single prop corresponding to a timer is referencing properties on this object. If we make direct
edits to this object, those changes will ripple out into all sorts of different areas in the code.
Components 77
Imagine if instead of immediately calling setState(), we had some sort of delay after modifying
the state object, like a call to a web service. If any children happened to re-render during that
period (like if their own state changed), the props they would use would be the new values, even
though we weren’t yet prepared for them to receive this change. This could lead to all sorts of odd,
unpredictable behavior.
We will explore the related concept of pure functions in a future chapter.
Both of the forms are wired up! Save app.js, reload the page, and try both creating and updating
timers. You can also click “Cancel” on an open form to close it:
The rest of our work resides within the timer. We need to:
• Wire up the trash button (deleting a timer)
• Implement the start/stop buttons and the timing logic itself
At that point, we’ll have a complete server-less solution.
Try it yourself: Before moving on to the next section, see how far you can get wiring up the trash
button by yourself. Move ahead afterwards and verify your solution is sound.
Components 78
Deleting timers
Adding the event handler to Timer
In Timer, we define a function to handle trash button click events:
const Timer = React.createClass({
handleTrashClick: function () {
this.props.onTrashClick(this.props.id);
},
render: function () {
// ...
And then use onClick to connect that function to the trash icon:
{/* Inside Timer.render() */}
We’ve yet to define the function that will be set as the prop onTrashClick(). But you can imagine
that when this event reaches the top (TimersDashboard), we’re going to need the id to sort out which
timer is being deleted. handleTrashClick() provides the id to this function.
Routing through EditableTimer
EditableTimer just proxies the function:
Components 79
// Inside EditableTimer
} else {
return (
);
}
Routing through EditableTimerList
As does EditableTimerList:
// Inside EditableTimerList.render()
const timers = this.props.timers.map((timer) => (
));
Implementing the delete function in TimersDashboard
The last step is to define the function in TimersDashboard that deletes the desired timer from the
state array. There are many ways to accomplish this in JavaScript. Don’t sweat it if your solution
was not the same or if you didn’t quite work one out.
We add our handler function that we ultimately pass down as a prop:
Components 80
// Inside TimersDashboard
handleEditFormSubmit: function (attrs) {
this.updateTimer(attrs);
},
handleTrashClick: function (timerId) {
this.deleteTimer(timerId);
},
deleteTimer() uses Array’s filter() method to return a new array with the timer object that has
an id matching timerId removed:
// Inside TimersDashboard
deleteTimer: function (timerId) {
this.setState({
timers: this.state.timers.filter(t => t.id !== timerId),
});
},
render: function () {
Finally, we pass down handleTrashClick() as a prop:
{/* Inside TimersDashboard.render() */}
Array’s filter() method accepts a function that is used to “test” each element in the array.
It returns a new array containing all the elements that “passed” the test. If the function
returns true, the element is kept.
Save app.js and reload the app. Low and behold, you can delete timers:
Components 81
Adding timing functionality
Create, update, and delete (CRUD) capability is now in place for our timers. The next challenge:
making these timers functional.
There are several different ways we can implement a timer system. The simplest approach would
be to have a function update the elapsed property on each timer every second. But this is severely
limited. What happens when the app is closed? The timer should continue “running.”
This is why we’ve included the timer property runningSince. A timer is initialized with elapsed
equal to 0. When a user clicks “Start”, we do not increment elapsed. Instead, we just set
runningSince to the start time.
We can then use the difference between the start time and the current time to render the time for
the user. When the user clicks “Stop”, the difference between the start time and the current time is
added to elapsed. runningSince is set to null.
Therefore, at any given time, we can derive how long the timer has been running by taking
Date.now() - runningSince and adding it to the total accumulated time (elapsed). We’ll calculate
this inside the Timer component.
For the app to truly feel like a running timer, we want React to constantly perform this operation
and re-render the timers. But elapsed and runningSince will not be changing while the timer is
running. So the one mechanism we’ve seen so far to trigger a render() call will not be sufficient.
Instead, we can use React’s forceUpdate() method. This forces a React component to re-render. We
can call it on an interval to yield the smooth appearance of a live timer.
Components 82
Adding a forceUpdate() interval to Timer
helpers.renderElapsedString() accepts an optional second argument, runningSince. It will add
the delta of Date.now() - runningSince to elapsed and use the function millisecondsToHuman()
to return a string formatted as HH:MM:SS.
We will establish an interval to run forceUpdate() after the component mounts:
const Timer = React.createClass({
componentDidMount: function () {
this.forceUpdateInterval = setInterval(() => this.forceUpdate(), 50);
},
componentWillUnmount: function () {
clearInterval(this.forceUpdateInterval);
},
handleTrashClick: function () {
this.props.onTrashClick(this.props.id);
},
render: function () {
const elapsedString = helpers.renderElapsedString(
this.props.elapsed, this.props.runningSince
);
return (
In componentDidMount(), we use the JavaScript function setInterval(). This will invoke the
function forceUpdate() once every 50 ms, causing the component to re-render. We set the return
of setInterval() to this.forceUpdateInterval.
In componentWillUnmount(), we use clearInterval() to stop the interval this.forceUpdateInterval.
componentWillUnmount() is called before a component is removed from the app. This will happen
if a timer is deleted. We want to ensure we do not continue calling forceUpdate() after the timer
has been removed from the page. React will throw errors.
setInterval() accepts two arguments. The first is the function you would like to call
repeatedly. The second is the interval on which to call that function (in milliseconds).
setInterval() returns a unique interval ID. You can pass this interval ID to
clearInterval() at any time to halt the interval.
You might ask: Wouldn’t it be more efficient if we did not continuously call forceUpdate()
on timers that are not running?
Indeed, we would save a few cycles. But it would not be worth the added code complexity.
React will call render() which performs some inexpensive operations in JavaScript. It will
then compare this result to the previous call to render() and see that nothing has changed.
It stops there — it won’t attempt any DOM manipulation.
Components 83
The 50 ms interval was not derived scientifically. Selecting an interval that’s too high
will make the timer look unnatural. It would jump unevenly between values. Selecting
an interval that’s too low would just be an unnecessary amount of work. A 50 ms interval
looks good to humans and is ages in computerland.
Try it out
Save app.js and reload. The first timer should be running.
We’ve begun to carve out the app’s real utility! We need only wire up the start/stop button and our
server-less app will be feature complete.
Add start and stop functionality
The action button at the bottom of each timer should display “Start” if the timer is paused and “Stop”
if the timer is running. It should also propagate events when clicked, depending on if the timer is
being stopped or started.
We could build all of this functionality into Timer. We could have Timer decide to render one HTML
snippet or another depending on if it is running. But that would be adding more responsibility and
complexity to Timer. Instead, let’s make the button its own React component.
Add timer action events to Timer
Let’s modify Timer, anticipating a new component called TimerActionButton. This button just needs
to know if the timer is running. It also needs to be able to propagate two events, onStartClick() and
onStopClick(). These events will eventually need to make it all the way up to TimersDashboard,
which can modify runningSince on the timer.
First, the event handlers:
// Inside Timer
componentWillUnmount: function () {
clearInterval(this.forceUpdateInterval);
},
handleStartClick: function () {
this.props.onStartClick(this.props.id);
},
handleStopClick: function () {
this.props.onStopClick(this.props.id);
},
// ...
Then, inside render(), we’ll declare TimerActionButton at the bottom of the outermost div:
Components 84
{/* At the bottom of `Timer.render()`` */}
);
We use the same technique used in other click-handlers: onClick on the HTML element specifies a
handler function in the component that invokes a prop-function, passing in the timer’s id.
We use !! here to derive the boolean prop timerIsRunning for TimerActionButton. !!
returns false when runningSince is null.
Create TimerActionButton
Create the TimerActionButton component now:
const TimerActionButton = React.createClass({
render: function () {
if (this.props.timerIsRunning) {
return (
Stop
);
} else {
return (
Start
);
}
},
});
Components 85
We render one HTML snippet or another based on this.props.timerIsRunning.
You know the drill. Now we run these events up the component hierarchy, all the way up to
TimersDashboard where we’re managing state:
Run the events through EditableTimer and EditableTimerList
First EditableTimer:
// Inside EditableTimer
} else {
return (
);
}
And then EditableTimerList:
// Inside EditableTimerList
const timers = this.props.timers.map((timer) => (
));
Components 86
Finally, we define these functions in TimersDashboard. They should hunt through the state timers
array using map, setting runningSince appropriately when they find the matching timer.
First we define the handling functions:
// Inside TimersDashboard
handleTrashClick: function (timerId) {
this.deleteTimer(timerId);
},
handleStartClick: function (timerId) {
this.startTimer(timerId);
},
handleStopClick: function (timerId) {
this.stopTimer(timerId);
},
And then startTimer() and stopTimer():
deleteTimer: function (timerId) {
this.setState({
timers: this.state.timers.filter(t => t.id !== timerId),
});
},
startTimer: function (timerId) {
const now = Date.now();
this.setState({
timers: this.state.timers.map((timer) => {
if (timer.id === timerId) {
return Object.assign({}, timer, {
runningSince: now,
});
} else {
return timer;
}
}),
});
},
stopTimer: function (timerId) {
const now = Date.now();
this.setState({
timers: this.state.timers.map((timer) => {
Components 87
if (timer.id === timerId) {
const lastElapsed = now - timer.runningSince;
return Object.assign({}, timer, {
elapsed: timer.elapsed + lastElapsed,
runningSince: null,
});
} else {
return timer;
}
}),
});
},
render: function () {
Finally, we pass them down as props:
{/* Inside TimerDashboard.render() */}
When startTimer comes across the relevant timer within its map call, it sets the property runningSince
to the current time.
stopTimer calculates lastElapsed, the amount of time that the timer has been running for since it
was started. It adds this amount to elapsed and sets runningSince to null, “stopping” the timer.
Try it out
Save app.js, reload, and behold! You can now create, update, and delete timers as well as actually
use them to time things:
Components 88
This is excellent progress. But, without a connection to a server, our app is ephemeral. If we refresh
the page, we lose all of our timer data. Our app does not have any persistence.
A server can give us persistence. We’ll have our server write all changes to timer data to a file.
Instead of hard-coding state inside of the TimersDashboard component, when our app loads it will
query the server and construct its timer state based on the data the server provides. We’ll then have
our React app notify the server about any state changes, like when a timer is started.
Communicating with a server is the last big major building block you’ll need to develop and
distribute real-world web applications with React.
Methodology review
While building our time-logging app, we learned and applied a methodology for building React
apps. Again, those steps were:
1. Break the app into components
We mapped out the component structure of our app by examining the app’s working UI. We
then applied the single-responsibility principle to break components down so that each had
minimal viable functionality.
2. Build a static version of the app
Our bottom-level (user-visible) components rendered HTML based on static props, passed
down from parents.
3. Determine what should be stateful
We used a series of questions to deduce what data should be stateful. This data was represented
in our static app as props.
4. Determine in which component each piece of state should live
We used another series of questions to determine which component should own each piece of
state. TimersDashboard owned timer state data and ToggleableTimerForm and EditableTimer
both held state pertaining to whether or not to render a TimerForm.
Components 89
5. Hard-code initial states
We then wrote state-owners’ getInitialState functions with hard-coded values.
6. Add inverse data flow
We added interactivity by decorating buttons with onClick handlers. These called functions
that were passed in as props down the hierarchy from whichever component owned the
relevant state being manipulated.
The final step is 7. Add server communication. We’ll tackle this in the next chapter.
Chapter Exercises
1. Modify the Timer component so that the “Edit” and “Trash” buttons only display
when the mouse is hovering over a given timer. onMouseEnter and onMouseLeave
will serve as your event attributes in much the same way as onClick has.
2. Add validation to the title and project fields on timers. They should not be blank.
An error message should be displayed above the field if the validation fails. Check
out the forms page on Semantic UI for the HTML for form errors: http://semanticui.com/collections/form.html#field-error²⁶.
²⁶http://semantic-ui.com/collections/form.html#field-error
Components & Servers
Introduction
In the last chapter, we used a methodology to construct a React app. State management of timers
takes place in the top-level component TimersDashboard. As in all React apps, data flows from the
top down through the component tree to leaf components. Leaf components communicate events to
state managers by calling prop-functions.
At the moment, TimersDashboard has a hard-coded initial state. Any mutations to the state will
only live as long as the browser window is open. That’s because all state changes are happening
in-memory inside of React. We need our React app to communicate with a server. The server will
be in charge of persisting the data. In this app, data persistence happens inside of a file, data.json.
EditableTimer and ToggleableTimerForm also have hard-coded initial state. But because this state
is just whether or not their forms are open, we don’t need to communicate these state changes to
the server. We’re OK with the forms starting off closed every time the app boots.
Preparation
To help you get familiar with the API for this project and working with APIs in general, we have a
short section where we make requests to the API outside of React.
curl
We’ll use a tool called curl to make more involved requests from the command line.
OS X users should already have curl installed.
Windows users can download and install curl here: https://curl.haxx.se/download.html²⁷.
server.js
Included in the root of your project folder is a file called server.js. This is a Node.js server
specifically designed for our time-tracking app.
You don’t have to know anything about Node.js or about servers in general to work with
the server we’ve supplied. We’ll provide the guidance that you need.
²⁷https://curl.haxx.se/download.html
Components & Servers 91
server.js uses the file data.json as its “store.” The server will read and write to this file to persist
data. You can take a look at that file to see the initial state of the store that we’ve provided.
server.js will return the contents of data.json when asked for all items. When notified, the server
will reflect any updates, deletes, or timer stops and starts in data.json. This is how data will be
persisted even if the browser is reloaded or closed.
Before we start working with the server, let’s briefly cover its API. Again, don’t be concerned if this
outline is a bit perplexing. It will hopefully become clearer as we start writing some code.
The Server API
Our ultimate goal in this chapter is to replicate state changes on the server. We’re not going to
move all state management exclusively to the server. Instead, the server will maintain its state (in
data.json) and React will maintain its state (in this case, within this.state in TimersDashboard).
We’ll demonstrate later why keeping state in both places is desirable.
TimersDashboard communicates with the server
If we perform an operation on the React (“client”) state that we want to be persisted, then we also
need to notify the server of that state change. This will keep the two states in sync. We’ll consider
these our “write” operations. The write operations we want to send to the server are:
• A timer is created
Components & Servers 92
• A timer is updated
• A timer is deleted
• A timer is started
• A timer is stopped
We’ll have just one read operation: requesting all of the timers from the server.
HTTP APIs
This section assumes a little familiarity with HTTP APIs. If you’re not familiar with HTTP
APIs, you may want to read up on them²⁸ at some point.
However, don’t be deterred from continuing with this chapter for the time being. Essentially
what we’re doing is making a “call” from our browser out to a local server and
conforming to a specified format.
text/html endpoint
GET /
This entire time, server.js has actually been responsible for serving the app. When your browser
requests localhost:3000/, the server returns the file index.html. index.html loads in all of our
JavaScript/React code.
Note that React never makes a request to the server at this path. This is just used by the
browser to load the app. React only communicates with the JSON endpoints.
JSON endpoints
data.json is a JSON document. As touched on in the last chapter, JSON is a format for storing
human-readable data objects. We can serialize JavaScript objects into JSON. This enables JavaScript
objects to be stored in text files and transported over the network.
data.json contains an array of objects. While not strictly JavaScript, the data in this array can be
readily loaded into JavaScript.
In server.js, we see lines like this:
²⁸http://www.andrewhavens.com/posts/20/beginners-guide-to-creating-a-rest-api/
Components & Servers 93
fs.readFile(DATA_FILE, function(err, data) {
const timers = JSON.parse(data);
// ...
});
data is a string, the JSON. JSON.parse() converts this string into an actual JavaScript array of
objects.
GET /api/timers
Returns a list of all timers.
POST /api/timers
Accepts a JSON body with title, project, and id attributes. Will insert a new timer object into its
store.
POST /api/timers/start
Accepts a JSON body with the attribute id and start (a timestamp). Hunts through its store and
finds the timer with the matching id. Sets its runningSince to start.
POST /api/timers/stop
Accepts a JSON body with the attribute id and stop (a timestamp). Hunts through its store and finds
the timer with the matching id. Updates elapsed according to how long the timer has been running
(stop - runningSince). Sets runningSince to null.
PUT /api/timers
Accepts a JSON body with the attributes id and title and/or project. Hunts through its store and
finds the timer with the matching id. Updates title and/or project to new attributes.
DELETE /api/timers
Accepts a JSON body with the attribute id. Hunts through its store and deletes the timer with the
matching id.
Playing with the API
If your server is not booted, make sure to boot it:
npm run server
You can visit the endpoint /api/timers endpoint in your browser and see the JSON response
(localhost:3000/api/timers²⁹). When you visit a new URL in your browser, your browser makes a
GET request. So our browser calls GET /api/timers and the server returns all of the timers:
²⁹localhost:3000/api/timers
Components & Servers 94
Note that the server stripped all of the extraneous whitespace in data.json, including newlines, to
keep the payload as small as possible. Those only exist in data.json to make it human-readable.
We can only easily use the browser to make GET requests. For writing data — like starting and
stopping timers — we’ll have to make POST, PUT, or DELETE requests. We’ll use curl to play around
with writing data.
Run the following command from the command line:
curl -X GET localhost:3000/api/timers
The -X flag specifies which HTTP method to use. It should return a response that looks a bit like
this:
[{"title":"Mow the lawn","project":"House Chores","elapsed":5456099,"id":"0a4a79\
cb-b06d-4cb1-883d-549a1e3b66d7"},{"title":"Clear paper jam","project":"Office Ch\
ores","elapsed":1273998,"id":"a73c1d19-f32d-4aff-b470-cea4e792406a"},{"title":"P\
onder origins of universe","project":"Life Chores","id":"2c43306e-5b44-4ff8-8753\
-33c35adbd06f","elapsed":11750,"runningSince":"1456225941911"}]
You can start one of the timers by issuing a PUT request to the /api/timers/start endpoint. We
need to send along the id of one of the timers and a start timestamp:
Components & Servers 95
curl -X POST \
-H 'Content-Type: application/json' \
-d '{"start":1456468632194,"id":"a73c1d19-f32d-4aff-b470-cea4e792406a"}' \
localhost:3000/api/timers/start
The -H flag sets a header for our HTTP request, Content-Type. We’re informing the server that the
body of the request is JSON.
The -d flag sets the body of our request. Inside of single-quotes '' is the JSON data.
When you press enter, curl will quickly return without any output. The server doesn’t return
anything on success for this endpoint. If you open up data.json, you will see that the timer you
specified now has a runningSince property, set to the value we specified as start in our request.
If you’d like, you can play around with the other endpoints to get a feel for how they work. Just be
sure to set the appropriate method with -X and to pass along the JSON Content-Type for the write
endpoints.
We’ve written a small library, client, to aid you in interfacing with the API in JavaScript.
Note that the backslash \ above is only used to break the command out over multiple lines
for readability.
Tool tip: jq
If you want to parse and process JSON on the command line, we highly recommend the tool “jq.”
You can pipe curl responses directly into jq to have the response pretty-formatted:
curl -X GET localhost:3000/api/timers | jq '.'
You can also do some powerful manipulation of JSON, like iterating over all objects in the response
and returning a particular field. In this example, we extract just the id property of every object in
an array:
curl -X GET localhost:3000/api/timers | jq '.[] | { id }'
You can download jq here: https://stedolan.github.io/jq/.
https://stedolan.github.io/jq/
Components & Servers 96
Loading state from the server
Right now, we set initial state in TimersDashboard by hardcoding a JavaScript object, an array of
timers. Let’s modify this function to load data from the server instead.
We’ve written the client library that your React app will use to interact with the server, client. The
library is defined in public/client.js. We’ll use it first and then take a look at how it works in the
next section.
The GET /api/timers endpoint provides a list of all timers, as represented in data.json. We can use
client.getTimers() to call this endpoint from our React app. We’ll do this to “hydrate” the state
kept by TimersDashboard.
When we call client.getTimers(), the network request is made asynchronously. The function call
itself is not going to return anything useful:
// Wrong
// `getTimers()` does not return the list of timers
const timers = client.getTimers();
Instead, we can pass getTimers() a success function. getTimers() will invoke that function after it
hears back from the server if the server successfully returned a result. getTimers() will invoke the
function with a single argument, the list of timers returned by the server:
// Passing `getTimers()` a success function
client.getTimers((serverTimers) => (
// do something with the array of timers, `serverTimers`
));
client.getTimers() uses the Fetch API, which we cover in the next section. For our
purposes, the important thing to know is that when getTimers() is invoked, it fires off
the request to the server and then returns control flow immediately. The execution of our
program does not wait for the server’s response. This is why getTimers() is called an
asynchronous function.
The success function we pass to getTimers() is called a callback. We’re saying: “When
you finally hear back from the server, if it’s a successful response, invoke this function.”
This asynchronous paradigm ensures that execution of our JavaScript is not blocked by
I/O.
While our initial instinct might be to try to load the timers from the server in getInitialState(),
we don’t want a request to the server to hold up the mounting of our app. Instead, we should render
a blank canvas for the app by setting timers in state to []. This will allow all components to mount
and perform their initial render. Then, we can populate the app by making a request to the server
and setting the state:
Components & Servers 97
const TimersDashboard = React.createClass({
getInitialState: function () {
return {
timers: [],
};
},
componentDidMount: function () {
this.loadTimersFromServer();
setInterval(this.loadTimersFromServer, 5000);
},
loadTimersFromServer: function () {
client.getTimers((serverTimers) => (
this.setState({ timers: serverTimers })
)
);
},
// ...
A timeline is the best medium for illustrating what happens:
1. Before initial render
React calls getInitialState() on TimersDashboard. An object with the property timers, a
blank array, is returned.
2. The initial render
React then calls render() on TimersDashboard. In order for the render to complete, EditableTimerList
and ToggleableTimerForm — its two children — must be rendered.
3. Children are rendered
EditableTimerList has its render method called. Because it was passed a blank data array, it
simply produces the following HTML output:
ToggleableTimerForm renders its HTML, which is the “+” button.
4. Initial render is finished
With its children rendered, the initial render of TimersDashboard is finished and the HTML
is written to the DOM.
5. componentDidMount is invoked
Now that the component is mounted, componentDidMount() is called on TimersDashboard.
Components & Servers 98
This method calls loadTimersFromServer(). In turn, that function calls client.getTimers().
That will make the HTTP request to our server, requesting the list of timers. When client
hears back, it invokes our success function.
On invocation, the success function is passed one argument, serverTimers. This is the array
of timers returned by the server. We then call setState(), which will trigger a new render.
The new render populates our app with EditableTimer children and all of their children. The
app is fully loaded and at an imperceptibly fast speed for the end user.
We also do one other interesting thing in componentDidMount. We use setInterval() to ensure
loadTimersFromServer() is called every 5 seconds. While we will be doing our best to mirror state
changes between client and server, this hard-refresh of state from the server will ensure our client
will always be correct should it shift from the server.
The server is considered the master holder of state. Our client is a mere replica. This becomes
incredibly powerful in a multi-instance scenario. If you have two instances of your app running
— in two different tabs or two different computers — changes in one will be pushed to the other
within five seconds.
Try it out
Let’s have fun with this now. Save app.js and reload the app. You should see a whole new list of
timers, driven by data.json. Any action you take will be wiped out within five seconds. Every five
seconds, state is restored from the server. For instance, try deleting a timer and witness it resiliently
spring back unscathed. Because we’re not telling the server about these actions, its state remains
unchanged.
On the flip-side, you can try modifying data.json. Notice how any modifications to data.json will
be propagated to your app in under five seconds. Neat.
We’re loading the initial state from the server. We have an interval function in place to ensure the
client app’s state does not drift from the server’s in a multi-instance scenario.
We’ll need to inform our server of the rest of our state changes: creates, updates (including starts
and stops), and deletes. But first, let’s pop open the logic behind client to see how it works.
While it is indeed neat that changes to our server data is seamlessly propagated to our view,
in certain applications — like messaging — five seconds is almost an eternity. We’ll cover
the concept of long-polling in a future app. Long-polling enables changes to be pushed to
clients near instantly.
client
If you open up client.js, the first method defined in the library is getTimers():
Components & Servers 99
function getTimers(success) {
return fetch('/api/timers', {
headers: {
Accept: 'application/json',
},
}).then(checkStatus)
.then(parseJSON)
.then(success);
}
We are using the new Fetch API to perform all of our HTTP requests. Fetch’s interface should look
relatively familiar if you’ve ever used XMLHttpRequest or jQuery’s ajax().
Fetch
Until Fetch, JavaScript developers had two options for making web requests: Use XMLHttpRequest
which is supported natively in all browsers or import a library that provides a wrapper around it
(like jQuery’s ajax()). Fetch provides a better interface than XMLHttpRequest. And while Fetch is
still undergoing standardization, it is already supported by a few major browsers. At the time of
writing, Fetch is turned on by default in Firefox 39 and above and Chrome 42 and above.
Until Fetch is more widely adopted by browsers, it’s a good idea to include the library just in case.
We’ve already done so inside index.html:
As we can see in client.getTimers(), fetch() accepts two arguments:
• The path to the resource we want to fetch
• An object of request parameters
By default, Fetch makes a GET request, so we’re telling Fetch to make a GET request to /api/timers.
We also pass along one parameter: headers, the HTTP headers in our request. We’re telling the
server we’ll accept only a JSON response.
Attached to the end of our call to fetch(), we have a chain of .then() statements:
Components & Servers 100
}).then(checkStatus)
.then(parseJSON)
.then(success);
To understand how this works, let’s first review the functions that we pass to each .then()
statement:
• checkStatus(): This function is defined inside of client.js. It checks if the server returned
an error. If the server returned an error, checkStatus() logs the error to the console.
• parseJSON(): This function is also defined inside of client.js. It takes the response object
emitted by fetch() and returns a JavaScript object.
• success(): This is the function we pass as an argument to getTimers(). getTimers() will
invoke this function if the server successfully returned a response.
Fetch returns a promise. While we won’t go into detail about promises, as you can see here a promise
allows you to chain .then() statements. We pass each .then() statement a function. What we’re
essentially saying here is: “Fetching the timers from /api/timers then check the status code returned
by the server. Then, extract the JavaScript object from the response. Then, pass that object to the
success function.”
At each stage of the pipeline, the result of the previous statement is passed as the argument to the
next one:
1. When checkStatus() is invoked, it’s passed a Fetch response object that fetch() returns.
2. checkStatus(), after verifying the response, returns the same response object.
3. parseJSON() is invoked and passed the response object returned by checkStatus().
4. parseJSON() returns the JavaScript array of timers returned by the server.
5. success() is invoked with the array of timers returned by parseJSON().
We could attach an infinite number of .then() statements to our pipeline. This pattern enables
us to chain multiple function calls together in an easy-to-read format that supports asynchronous
functions like fetch().
It’s OK if you’re still uncomfortable with the concept of promises. We’ve written all the
client code for this chapter for you, so you won’t have trouble completing this chapter. You
can come back afterwards to play around with client.js and get a feel for how it works.
You can read more about JavaScript’s Fetch here³⁰ and promises here³¹.
³⁰https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
³¹https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
Components & Servers 101
Looking at the rest of the functions in client.js, you’ll note the methods contain much of the same
boilerplate with small differences based on the endpoint of the API we are calling.
We just looked at getTimers() which demonstrates reading from the server. We’ll look at one more
function, one that writes to the server.
startTimer() makes a POST request to the /api/timers/start endpoint. The server needs the id of
the timer and the start time. That request method looks like:
function startTimer(data) {
return fetch('/api/timers/start', {
method: 'post',
body: JSON.stringify(data),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
}).then(checkStatus);
}
In addition to headers, the request parameters object that we pass to fetch() has two more
properties:
method: 'post',
body: JSON.stringify(data),
Those are:
• method: The HTTP request method. fetch() defaults to a GET request, so we specify we’d like
a POST here.
• body: The body of our HTTP request, the data we’re sending to the server.
startTimer() expects an argument, data. This is the object that will be sent along in the body of
our request. It contains the properties id and start. An invocation of startTimer() might look like
this:
Components & Servers 102
// Example invocation of `startTimer()`
startTimer(
{
id: "bc5ea63b-9a21-4233-8a76-f4bca9d0a042",
start: 1455584369113,
}
);
In this example, the body of our request to the server will look like this:
{
"id": "bc5ea63b-9a21-4233-8a76-f4bca9d0a042",
"start": 1455584369113
}
The server will extract the id and start timestamp from the body and “start” the timer.
We don’t pass startTimers() a success function. Our app does not need data from the server for
this request and indeed our server will not return anything besides an “OK” anyway.
getTimers() is our only read operation and therefore the only one we’ll pass a success function.
The rest of our calls to the server are writes. Let’s implement those now.
Sending starts and stops to the server
We can use the methods startTimer() and stopTimer() on client to make calls to the appropriate
endpoints on the server. We just need to pass in an object that includes the id of the timer as well
as the time it was started/stopped:
// Inside TimersDashboard
// ...
startTimer: function (timerId) {
const now = Date.now();
this.setState({
timers: this.state.timers.map((timer) => {
if (timer.id === timerId) {
return Object.assign({}, timer, {
runningSince: now,
});
} else {
return timer;
Components & Servers 103
}
}),
});
client.startTimer(
{ id: timerId, start: now }
);
},
stopTimer: function (timerId) {
const now = Date.now();
this.setState({
timers: this.state.timers.map((timer) => {
if (timer.id === timerId) {
const lastElapsed = now - timer.runningSince;
return Object.assign({}, timer, {
elapsed: timer.elapsed + lastElapsed,
runningSince: null,
});
} else {
return timer;
}
}),
});
client.stopTimer(
{ id: timerId, stop: now }
);
},
render: function () {
// ...
You might ask: Why do we still manually make the state change within React? Can’t we just inform
the server of the action taken and then update state based on the server, the source of truth? Indeed,
the following implementation is valid:
Components & Servers 104
startTimer: function (timerId) {
const now = Date.now();
client.startTimer(
{ id: timerId, start: now }
).then(loadTimersFromServer);
},
We can chain a .then() to startTimer() as that function returns our original promise object.
The last stage of the startTimer() pipeline would then be invoking the function loadTimersFromServer().
So immediately after the server processes our start timer request, we would make a
subsequent request asking for the latest list of timers. This response would contain the now-running
timer. React’s state updates and the running timer would then be reflected in the UI.
Again, this is valid. However, the user experience will leave something to be desired. Right now,
clicking the start/stop button gives instantaneous feedback because the state changes locally and
React immediately re-renders. If we waited to hear back from the server, there might be a noticeable
delay between the action (mouse click) and the response (timer starts running). You can try it yourself
locally, but the delay would be most noticeable if the request had to go out over the internet.
What we’re doing here is called optimistic updating. We’re updating the client locally before
waiting to hear from the server. This duplicates our state update efforts, as we perform updates
on both the client and the server. But it makes our app as responsive as possible.
The “optimism” we have here is that the request will succeed and not fail with an error.
Using the same pattern as we did with starts and stops, see if you can implement creates, updates,
and deletes on your own. Come back and compare your work with the next section.
Optimistic updating: Validations
Whenever we optimistic update, we always try to replicate whatever restrictions the server would
have. This way, our client state changes under the same conditions as our server state.
For example, imagine if our server enforced that a timer’s title cannot contain symbols. But the
client did not enforce such a restriction. What would happen?
A user has a timer named Gardening. He feels a bit cheeky and renames it Gardening :P. The UI
immediately reflects his changes, displaying Gardening :P as the new name of the timer. Satisfied,
the user is about to get up and grab his shears. But wait! His timer’s name suddenly snaps back to
Gardening.
To successfully pull off eager updating, we must diligently replicate the code that manages state
changes on both the client and the server. Furthermore, in a production app we should surface any
Components & Servers 105
errors the request to the server returns in the event that there is an inconsistency in the code or a
fluke (the server is down). Implementing this feature for this app is left as an exercise at the end of
the chapter.
Sending creates, updates, and deletes to the server
// Inside TimersDashboard
// ...
createTimer: function (timer) {
const t = helpers.newTimer(timer);
this.setState({
timers: this.state.timers.concat(t),
});
client.createTimer(t);
},
updateTimer: function (attrs) {
this.setState({
timers: this.state.timers.map((timer) => {
if (timer.id === attrs.id) {
return Object.assign({}, timer, {
title: attrs.title,
project: attrs.project,
});
} else {
return timer;
}
}),
});
client.updateTimer(attrs);
},
deleteTimer: function (timerId) {
this.setState({
timers: this.state.timers.filter(t => t.id !== timerId),
});
client.deleteTimer(
{ id: timerId }
Components & Servers 106
);
},
startTimer: function (timerId) {
// ...
Recall that, in createTimer() and updateTimer() respectively, the timer and attrs objects contain
an id property, as required by the server.
For creates, we need to send a full timer object. It should have an id, a title, and a project. For
updates, we can send an id along with just whatever attributes are being updated. Right now, we
always send along title and project regardless of what has changed. But it’s worth noting this
difference as it’s reflected in the variable names that we are using (timer vs attrs).
Give it a spin
We are now sending all of our state changes to the server. Save app.js and reload the app. Add
some timers, start some timers, and refresh and note that everything is persisted. You can even
make changes to your app in one browser tab and see the changes propagate to another tab.
Next up
We’ve worked through a reusable methodology for building React apps and now have an understanding
of how we connect a React app to a web server. Armed with these concepts, you’re already
equipped to build a variety of dynamic web applications.
In imminent chapters, we’ll cover a variety of different component types that you encounter across
the web (like forms and date pickers). We’ll also explore state management paradigms for more
complex applications.
Stay tuned!
Components & Servers 107
Chapter Exercises
Right now, if a write to the server fails the app does not surface the issue. There are few
ways we could surface that there was an error.
For both exercises below, note that checkStatus() will throw an error if the server returns a
bad response (a non-200). In a promise chain, you can add a .catch() that will be executed
if a call in the chain has emitted an error. You can have each client method accept an
additional argument, onError. To have this function invoked in the case a server returns
an error, you can insert a .catch() call after .then(checkStatus), like this:
function createTimer(data, onError) {
return fetch('/api/timers', {
// request parameters here
}).then(checkStatus)
.catch(onError);
}
1. Modify how TimersDashboard uses client such that an error is displayed to the
user if communication with the server fails.
You can use a Semantic UI “Segment” to contain the error message: http://semanticui.com/elements/segment.html#emphasis³²
2. Building off of the first exercise, modify TimerForm so that it accepts new props,
fieldErrors. TimersDashboard can maintain this state. If TimersDashboard fails to
communicate with the server, it should pop the form submission that failed back
open and pass it the errors so that they can be displayed to the user.
Check out Semantic UI’s form field errors for the HTML: http://semanticui.com/collections/form.html#field-error³³
³²http://semantic-ui.com/elements/segment.html#emphasis
³³http://semantic-ui.com/collections/form.html#field-error
JSX and the Virtual DOM
React Uses a Virtual DOM
React works differently than many earlier front-end JavaScript frameworks in that instead of
working with the browser’s DOM, it builds a virtual representation of the DOM. By virtual,
we mean a tree of JavaScript objects that represent the “actual DOM”. More on this in a minute.
In React, we do not directly manipulate the actual DOM. Instead, we must manipulate the virtual
representation and let React take care of changing the browser’s DOM.
As we’ll see in this chapter, this is a very powerful feature but it requires us to think differently
about how we build web apps.
Why Not Modify the Actual DOM?
It’s worth asking: why do we need a Virtual DOM? Can’t we just use the “actual-DOM”?
When we do “classic-“ (e.g. jQuery-) style web development, we would typically:
1. locate an element (using document.querySelector or document.getElementById) and then
2. modify that element directly (say, by calling .innerHTML() on the element).
This style of development is problematic in that:
• It’s hard to keep track of changes - it can become difficult keep track of current (and prior)
state of the DOM to manipulate it into the form we need
• It can be slow - modifying the actual-DOM is a costly operation, and modifying the DOM
on every change can cause poor performance
What is a Virtual DOM?
The Virtual DOM was created to deal with these issues. But what is the Virtual DOM anyway?
The Virtual DOM is a tree of JavaScript objects that represents the actual DOM.
One of the interesting reasons to use the Virtual DOM is the API it gives us. When using the Virtual
DOM we code as if we’re recreating the entire DOM on every update.
JSX and the Virtual DOM 109
This idea of re-creating the entire DOM results in an easy-to-comprehend development model:
instead of the developer keeping track of all DOM state changes, the developer simply returns the
DOM they wish to see. React takes care of the transformation behind the scenes.
This idea of re-creating the Virtual DOM every update might sound like a bad idea: isn’t it going
to be slow? In fact, React’s Virtual DOM implementation comes with important performance
optimizations that make it very fast.
The Virtual DOM will:
• use efficient diffing algorithms, in order to know what changed
• update subtrees of the DOM simultaneously
• batch updates to the DOM
All of this results in an easy-to-use and optimized way to build web apps.
Virtual DOM Pieces
Again, when building a web app in React, we’re not working directly with the browser’s “actual
DOM” directly, but instead a virtual representation of it. Our job is to provide React with enough
information to build a JavaScript object that represents what the browser will render.
But what does this Virtual DOM JavaScript object actually consist of?
React’s Virtual DOM is a tree of ReactElements.
Understanding the Virtual DOM, ReactElements, and how they interact with the “actual DOM” is
a lot easier to understand by working through some examples, which we’ll do below.
Q: Virtual DOM vs. Shadow DOM, are they the same thing? (A: No)
Maybe you’ve heard of the “Shadow DOM” and you’re wondering, is the Shadow DOM
the same thing as the Virtual DOM? The answer is no.
The Virtual DOM is a tree of JavaScript objects that represent the real DOM elements.
The Shadow DOM is a form of encapsulation on our elements. Think about using the