Hacking With React
Hacking With React
Paul Hudson
© 2016 Paul Hudson
This book is dedicated to my wife, who has been endlessly patient with me while I write and re-write
this book. No matter how hard I work, she works just as hard and has always been there for me.
CONTENTS
Contents
Welcome! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Introduction to JSX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
www.hackingwithreact.com @twostraws
CONTENTS
Mocking Ajax with Jest: Making an Asynchronous Test Become Synchronous . . . . . . 102
www.hackingwithreact.com @twostraws
Welcome! 1
Welcome!
Welcome to Hacking with React, a hands-on introduction to making a web app using React, ES6,
Jest and more.
What will I learn? We’ll cover lots of React, naturally. But we’ll also be using ES6, Babel, Webpack,
React Router, Jest, SuperAgent, Chance, ESLint and more.
Do you cover Flux or Redux? No, I’m afraid not. React has a steep learning curve all of its own
even before you add in JSX, React Router, Jest and others; as much as I like Redux, adding it into the
mix would have made things much too complicated for beginners. Perhaps if this book gets enough
interest I shall be able to add Redux material as part of a second project.
Do I need to know ES6/ECMAScript 2015? No. This book assumes you’re at least somewhat
familiar with JavaScript, but I try to explain ES6 features along the way. This is not an ES6 tutorial,
but at the same time I’m aware that many people haven’t used it before so I do my best.
Wait… do web browsers even support ES6 yet? No, but we’ll be using Babel to transpile ES6 to
regular old JavaScript that works in any browser.
Will I actually make anything, or is it all just theory? This book takes you through one complete
project that loads data using the GitHub API. I chose the GitHub API because it has public access to
interesting data. In order to help make the learning curve shallow, the first quarter of the book uses
very simple examples that evolve into the full project later on.
How come this book is so short? I’ve seen React books twice this length! It’s true: this is a short
book, but it’s short because I focus on teaching you React. I don’t discuss the history of React or
other components, and I don’t go off on any tangents comparing various options. This book teaches
you React in the shortest possible time, and I think you’ll be impressed by how quickly you learn.
Does the book cover testing? Yes, although I leave it to near the end. Heresy, I know.
I spotted an error / typo – what should I do? Email me at [email protected] and let
me know. Please send me the chapter name that contains the error otherwise it can be hard to track
down!
Can I get the source code for the finished project? Yup, it’s all on GitHub.
Who are you? Why should I care what you say? My name is Paul Hudson, and my apps are used
by the likes of MacLife magazine, Edge magazine, Fender, Virgin, Jamie Oliver, Odeon, Tesco, and
more. I’m an author in my spare time, having written PHP in a Nutshell, Ubuntu Unleashed and
Fedora Unleashed. Previously I was the editor of Linux Format magazine, but my writing has also
appeared in MacFormat magazine, Net magazine and TechRadar.
www.hackingwithreact.com @twostraws
Welcome! 2
You might like to know that I have two other “Hacking with…” books available online: Hacking with
Swift teaching iOS coding and Hacking with PHP teaching server-side web development. Yes, they
are both available online for free.
You will often find me on Stack Overflow answering questions about iOS, PHP, and React.
I love your stuff – are you available for hire? Get in touch at [email protected] and
let’s talk – I love coding, and I love writing, so being able to put them both together is pure magic
for me.
I have a question about something in the book. How can I contact you? Use [email protected]
if you want to email me. Alternatively, you should follow me on Twitter: @twostraws.
www.hackingwithreact.com @twostraws
Begin at the Beginning: Chapter One 3
dist/somefile.js
Code without a title is either a command to run on the command line, or is code presented just for
your reference – in which case I often prefix it with “The following code won’t work because…”.
There are some places where you’ll see code with a filename in the context of reading code in that
file rather than writing, but these places are clearly marked. For example, I might say “here’s how
your code should look if you’ve been following along…”
Anyway, that’s more than enough explanation. If you’re ready, let’s get started: open the Terminal
app and run these commands to create your project directory:
cd Desktop
mkdir hwr
cd hwr
npm init -y
If you’ve never used the Terminal command before, you should be able to find it using Spotlight on
OS X. If you’re using Windows, you should use the Command Prompt. If you’re using Linux, any
terminal emulator is fine.
The first command switches to your desktop, which makes finding your work easier. The second
command creates a new directory called hwr - short for Hacking with React - but you can call it
whatever you want within reason. Note: calling it “react” will cause problems, so please try to be
original!
www.hackingwithreact.com @twostraws
Begin at the Beginning: Chapter One 4
The third command navigates into the newly created hwr directory, which is where we’ll be working
for the remainder of this tutorial. This is your project directory, and you should run all future
commands from here. The final command creates a new project using npm, at which point you’re
ready to go.
Setting up a skeleton project takes just four commands in the terminal. It’s worth taking the time to get
comfortable in the terminal window, because we’ll be using it more in the future.
Before we do any coding, we have a bit of boring configuration to get through. This is unavoidable,
I’m afraid, but at the same time it’s hugely valuable: you’ll be spending the next few hours working
through this tutorial, so spending five minutes making sure your project is configured correctly will
make your life much easier!
In your terminal window, still in your project directory, run these commands:
Note: if you see “command not found” for npm, it means you don’t have Node.js installed. You need
to go to nodejs.org and install Node.js then try again.
Those npm commands take a little while to run because they pull in quite a few pieces of software.
If you’ve never used npm before, the first three commands mean “install this software for use while
I’m developing,” and the fourth means “install this software for use while I’m developing, but I’ll
also need it when my app is in production too.”
www.hackingwithreact.com @twostraws
Begin at the Beginning: Chapter One 5
If you were wondering, Babel is a tool that accepts ES6 and React code as input, and creates regular
JavaScript as output. This is important because the technologies we’ll be using aren’t compatible
with web browsers, so it’s Babel’s job to make it compatible in a process known as “transpiling” -
converting ES6 source code to old-school JavaScript.
With all that software installed, the next step is to tell them how they should work together. In your
project directory, look for a file named package.json and open it in a text editor. Please add these
lines just after where it says "license": "ISC":
package.json
"babel": {
"presets": [
"es2015",
"react"
]
},
Note that you need a comma at the end of the closing brace – that’s the } symbol. In case you’re not
sure where to put it, here’s a complete package.json file for reference:
package.json
1 {
2 "name": "hwr",
3 "version": "1.0.0",
4 "description": "",
5 "main": "index.js",
6 "scripts": {
7 "test": "echo \"Error: no test specified\" && exit 1"
8 },
9 "keywords": [],
10 "author": "",
11 "license": "ISC",
12 "babel": {
13 "presets": [
14 "es2015",
15 "react"
16 ]
17 },
18 "devDependencies": {
19 "babel-core": "^6.3.15",
20 "babel-loader": "^6.2.0",
21 "babel-preset-es2015": "^6.3.13",
www.hackingwithreact.com @twostraws
Begin at the Beginning: Chapter One 6
22 "babel-preset-react": "^6.3.13",
23 "react-hot-loader": "^1.3.0",
24 "webpack": "^1.12.9",
25 "webpack-dev-server": "^1.14.0"
26 },
27 "dependencies": {
28 "history": "^1.13.1",
29 "react": "^0.14.3",
30 "react-dom": "^0.14.3",
31 "react-router": "^1.0.2"
32 }
33 }
The last step in our configuration is to prepare Webpack, which is a tool that converts all our
source code into one finished app ready to ship to users. Webpack is controlled by a file named
webpack.config.js, which doesn’t exist yet. So, please paste the below into a file and save it as
webpack.config.js in your project directory:
webpack.config.js
www.hackingwithreact.com @twostraws
Begin at the Beginning: Chapter One 7
24 devServer: {
25 contentBase: './dist',
26 hot: true
27 },
28 plugins: [
29 new webpack.HotModuleReplacementPlugin()
30 ]
31 };
That configures Webpack to read all our source code and convert it into a single finished script called
bundle.js inside a directory called dist – short for “distribution”. We haven’t made that directory
yet, so please create it inside your project directory now. While you’re there, please also create one
called src – that’s short for “source” and it’s where we’ll be storing all the source code we write.
So, we have src for our source code, and dist for files ready to distribute to the world. The next step
is to add the bare minimum to those directories in order for our project to start working. To do that,
create a new file in src called index.js, giving it this content:
src/index.js
When the script runs, that will show a simple message so you can be sure everything is working
correctly.
In the dist directory, please create index.html with this content:
dist/index.html
1 <!DOCTYPE html>
2 <html>
3 <body>
4 <div id="app"></div>
5 <script src="bundle.js"></script>
6 </body>
7 </html>
There are two interesting things in there. First, <div id="app"></div> is where we’ll be asking
React to render our app in just a few moments. Second, <script src="bundle.js"></script> is
the finished script that Webpack builds for us by combining all our code into one single file.
With those two files created, you should be able to run webpack from your terminal window and
see output like this:
www.hackingwithreact.com @twostraws
Begin at the Beginning: Chapter One 8
Hash: 696d94eec4dc3cb40865
Version: webpack 1.12.9
Time: 1304ms
Asset Size Chunks Chunk Names
bundle.js 940 kB 0 [emitted] main
[0] multi main 52 bytes {0} [built]
+ 245 hidden modules
If you see errors like “command not found” after you run the webpack command, try this command:
That installs two key components globally to your system, so you will need to enter your computer’s
system password.
Once that completes, hopefully you should be able to run webpack and see green results.
www.hackingwithreact.com @twostraws
The Importance of using Webpack with React 9
Our bundle.js file contains all sorts of code that we didn’t write, which is the point of Webpack: it assembles (and
compresses!) all your libraries for you.
Running webpack straight from the command line proves that it works, but it’s not how we’ll be
using it. This is because alongside Webpack we also installed Webpack Dev Server, which is the
www.hackingwithreact.com @twostraws
The Importance of using Webpack with React 10
smartest, simplest way to build React applications – and it’s what we’ll be using from now on.
So, please run this command now:
webpack-dev-server
Your computer will think for a few seconds then lots of text will whizz by, but hopefully the last line
says “webpack: bundle is now VALID.”
Webpack Dev Server will build all your app’s code into one single file, then serve it up from the contents of your
dist directory.
Now for the clever bit: open your web browser and point it at the address http://localhost:8080. This
is your local computer running Webpack Dev Server – not only has it built your app for you, but
it’s also serving that app up so you can try it live.
Right now our code doesn’t do anything, but you should be able to to your web browser’s developer
console and see the message “React is up and running!” printed there.
• Chrome users: go to the View menu and choose Developer > JavaScript Console.
• Safari users: go to the Develop menu and choose Show Error Console. If you can’t see the
develop menu, go to Safari > Preferences, select the Advanced tab, then make sure the “Show
Develop menu in menu bar” checkbox is checked.
• Firefox users: go to the Tools menu and choose Web Developer > Web Console.
www.hackingwithreact.com @twostraws
The Importance of using Webpack with React 11
This shows that our basic configuration is all done, and it’s time to start some React coding.
Note: if you’re a Chrome user, you will find the React Chrome Developer Tools very useful when
debugging your React apps.
Stop what you’re doing and make sure you can find your web browser’s developer console – this is important
later on!
www.hackingwithreact.com @twostraws
Introduction to JSX 12
Introduction to JSX
Inside the src directory create a new subdirectory called pages, and in there please create the file
Detail.js. When working with React some developers like to use the file extension .jsx and others
like to use plain old .js – honestly it really doesn’t matter, so use whichever makes you happy. That
being said, if this is your first time using React I would suggest you stick with .js so you can follow
this tutorial more easily.
In Detail.js we’re going to make React render something really simple so you can see why we needed
to jump through all those Webpack and NPM hoops earlier. Modify Detail.js so it has this content:
src/pages/Detail.js
That’s seven lines of ES6 code, of which one is empty and two are just closing braces by themselves,
so hopefully nothing too challenging for you. But what’s interesting is the use of HTML, because
it’s right in the middle there and not surrounded by quotes. And yet it works, which is one of the
marvellous things about React: this is called JSX, and when your code gets built by Webpack that
JSX automatically gets converted into JavaScript.
If you were wondering, a “brace” is the name for the { and } symbol, with { being an opening brace
and } being a closing brace. You’ll be using these a lot!
Anyway, as you can see JSX looks pretty much like HTML, although there are a few exceptions
you’ll learn as you progress. Let’s take a look at each of the important lines of code:
• import React from ‘react’ loads the React library, which is pretty central to our whole
application and thus is required.
• class Detail extends React.Component { defines a new React component. React components
can be big (like pages) or small (like a custom component to render breadcrumbs) and they
are very flexible.
www.hackingwithreact.com @twostraws
Introduction to JSX 13
• render() { starts the render() method of our component. This is called by React when the
component needs to be drawn to the screen, and needs to return something that can be drawn
in the browser.
• What’s left in this class is just the closing brace } that ends the render() method and the
second closing brace that ends the class we’re creating.
• export default Detail; The “export” keyword means this component is being exposed to the
rest of our app to use, and “default” means it’s the only thing this class will expose.
This example has all the JSX on one line, but we’ll soon move to writing multi-line JSX. Multi-line
JSX introduces one important quirk you should be aware of now, which is that JSX automatically
trims whitespace between lines. So, if you’re following the code examples in this book and find some
missing whitespace between two lines, try putting them on one line and you’ll find it works.
Now, after all that code I’m sorry to say it won’t do anything just yet. Fortunately, that’s easily
fixed…
www.hackingwithreact.com @twostraws
Importing React Components using ES6 14
src/index.js
Save both index.js and Detail.js and, all being well, you should be able to return to your web browser
and see “This is React rendering HTML!” right there on the screen.
Before I explain what the new code does, try going back to Detail.js and modify its render method
to say “This is JSX being converted to HTML!” If you do that, then press save again, you’ll see some
magic happen: your web browser will automatically update to show the new message.
You don’t need to run any commands or indeed take any other action than saving your code –
Webpack Dev Server automatically detects the change and reloads your work. Hopefully you can
now see why it was so important to get the Webpack configuration right at the start of this tutorial,
because using this development set up (known as “hot loading”) makes coding substantially faster.
Note: if you don’t see any changes, just hit Refresh in your browser.
Now, let me explain what the new code in index.js does…
• import React from ‘react’ is a line you’ve seen before, and you’ll see again in this tutorial: it
just sucks in the main React library so we can start work.
• import ReactDOM from ‘react-dom’ is new, and imports the React tools required to render
to the DOM – that’s the name used for the document structure used to describe web pages
and other similar documents.
• import Detail from ‘./pages/Detail’ is where we import our new React component into our
app so that we can start using it.
www.hackingwithreact.com @twostraws
Importing React Components using ES6 15
• ReactDOM.render() is what kicks off the rendering of our entire app, and it takes two
parameters: some JSX to render and where to render it to.
• <Detail /> is the first parameter we ask React to render, and it’s the JSX name of our Detail
component.
• document.getElementById(‘app’) is the second parameter to the render method, and it
tells React we want it to render inside the HTML element named “app” – that’s inside the
index.html file we made earlier.
When our app gets built, that <Detail /> line automatically gets converted into the Detail
component we created inside Detail.js, which in turn has its render() method called so it draws to
the screen.
Now, before we continue you probably have some questions. Let me try to answer some:
• Why does Detail.js have a capital letter? This isn’t needed, but it’s stylistically preferred.
• How does JSX know what <Detail /> means? We don’t give the component a name inside
Detail.js, so instead the name comes from the way we import it: if you use import Bob from
'./pages/Detail'; then you could write <Bob /> and it would work just fine. (But please
don’t do that if you value your sanity!)
• Can I put lots of components in Detail.js? You can if you want to, but again it’s preferable
not to if you value your sanity. Stick to one component per file if you can.
• Do I have to render stuff inside my component? No, but React does need something to
render at this point. When you’re a more experienced React developer you’ll learn more about
this.
1. How to install Webpack, Babel and React for development with ES6.
2. How to create a basic React component and import it into an application.
3. How to write simple JSX to render content.
www.hackingwithreact.com @twostraws
What are React Props? 16
src/index.js
Note the new message="This is coming from props!" attribute to the Detail component. In
Detail.js we need to make it read from the message prop rather than a hard-coded string, but that’s
easy enough to do:
src/pages/Detail.js
www.hackingwithreact.com @twostraws
What are React Props? 17
Notice how I’ve written this.props.message inside braces? That’s because it’s JavaScript code rather
than plain text. When your app gets built that part gets executed as code so that the input message
is used rather than a hard-coded string. If you go back to your web browser you’ll see it should have
updated with the new message. And if you go into index.js and change the message there, it will
update again – great!
Before we continue, it’s important I clarify that props aren’t strictly read-only, and indeed you
can change them at any point if you wish. However, doing so is very strongly discouraged: you
should consider them read only for the component they belong to, and change them only from
the component that created them. That is, if you created a component and gave it some props you
can change those props later if you want to, but props you receive (i.e., this.props) should not be
changed.
www.hackingwithreact.com @twostraws
Generating Random Values for Our Page 18
You were probably still running Webpack Dev Server in there, but that’s OK - press Ctrl+C to quit
that, then run the npm command above. Once that’s finished you can restart Webpack Dev Server
again by running webpack-dev-server.
The Chance library generates realistic-looking random data, which means it can generate random
first names, last names, sentences of text, social security numbers and so on – this makes it a good
test case for printing out some information. To use it you need to import the library into Detail.js,
like this:
src/pages/Detail.js
You can then generate random first names inside the render() method like this:
src/pages/Detail.js
Just to make sure you’re following along, here’s how your Detail.js file should look once these
changes are made:
www.hackingwithreact.com @twostraws
Generating Random Values for Our Page 19
src/pages/Detail.js
If you save that file and look back in your web browser you’ll see “Hello, Emma!” or similar appear,
and you can hit refresh in your browser to see a different name.
So you can see we’re literally calling the first() method in the Chance library right there while
rendering, and I hope it’s clear that putting code into braces like this is very powerful indeed.
But there’s a catch, and it’s a big one. In fact, it’s a catch so big that fisherman will tell tall tales
about it for years to come…
www.hackingwithreact.com @twostraws
How to Write if/else Conditional Statements in JSX 20
src/pages/Detail.js
Everything inside those braces is ES6 code and will be executed then have its results put back into
the render. In this case, it means you’ll see random names every time you refresh your page.
However, that code is actually a very specific kind of code called an expression, which very roughly
means that it can be translated directly into a value. This is in comparison to another kind of code
called a statement, which is where you can for example create variables or perform some other kind
of action.
I realize this distinction might seem insignificant, but trust me: it’s important. The reason it’s
important is because you can only use expressions inside JSX braces, not full statements. For
example, {this.props.message} and {chance.first()} are both valid, but something like this is
not:
(If you were wondering, === is the recommended way of comparing values in JavaScript; if you
use == right now you should probably switch, because there’s a big difference between “truth” and
“truthy”.)
Now, you might very well think “I’m glad that kind of code isn’t allowed, because it’s so hard to
read!” And you’d be right: you can’t write if/else statements inside JSX. However, JavaScript is (very
loosely) a C-like language, which means it has inherited conditional syntax that lets you do if/else
as an expression.
Translated, that means there’s a way to rewrite that above statement as an expression, which in turn
means you can use it inside JSX. However, you should steel yourself for some pretty grim syntax.
Here it is:
www.hackingwithreact.com @twostraws
How to Write if/else Conditional Statements in JSX 21
src/pages/Detail.js
{chance.first() === 'John' ? console.log('Got John')
: console.log('Got someone else') }
src/pages/Detail.js
{
chance.first() == 'John'
? console.log('Got John')
: console.log('Got someone else')
}
You can put that into your component if you want to, but it’s just an example for demonstration –
we’ll be removing it shortly.
The opening and closing braces are old, but the bit in the middle is new and is called a ternary
expression because it’s made up of three parts: the condition (chance.first() == 'John'), what
to use if the condition is true (console.log('Got John')) and what to use if the condition is false
(console.log('Got someone else')).
The important part is the question mark and the colon: the “true” section comes after the question
mark, and the false part comes after the colon. It’s not at all obvious, and it really does make for ugly
code, but it is absolutely allowable inside JSX and so, like it or not, you’ll see ternary expressions all
over the place in React code.
Worse, you’ll often see double or even triple ternary expressions where the question marks and
colons stack up higher and higher to form a true/false tree. These are also allowed by JSX, but I’m
pretty sure they are disallowed by the Geneva Convention or something.
One of the few nice things about ternary expressions in JSX is that their result gets put straight into
your output. For example:
src/pages/Detail.js
render() {
return <p>
{
chance.first() == 'John'
? 'Hello, John!'
: 'Hello, world!'
}
</p>;
}
www.hackingwithreact.com @twostraws
How to Write if/else Conditional Statements in JSX 22
In that example, the true/false blocks of the ternary expression just contains a string, but that’s OK
because the string gets passed back into the JSX and will be displayed inside the <p> element.
So: be prepared to use these ternary expressions sometimes, often written entirely on one line, but
please remember they are easily abused!
www.hackingwithreact.com @twostraws
Using JSX to Render Several Elements at Once 23
src/pages/Detail.js
return <p>Hello, {chance.first()}!</p>;
OK, nice and easy again. Adding a random country name using Chance is done like this:
That will return “Australia” or other country names, ready to use, so you might think we could
modify our render() method to look something like this:
render() {
return <p>Hello, {chance.first()}.</p>
<p>You're from {chance.country({ full: true })}.</p>;
}
But if you try that you’ll find that React refuses to load your page. Inside your browser’s message
console you’ll see a wall of red error messages telling you that your JSX is invalid. So what went
wrong?
Well, as with ternary expressions this is another instance where you need to “look behind the
curtain” of JSX to see what’s going on: your render() method gets to return one and only one
value, and each of those <p> elements gets converted into code when your app is built. So, when
the app gets built, having two <p> elements means you’re trying to return two values, which isn’t
possible.
There is a solution, and it’s an easy one: wrap those two <p> elements inside another element, e.g.
<div> element. If you do that, JSX can clearly see the start and end of a single return value, and
everything works again.
So, again, here’s the bad code:
www.hackingwithreact.com @twostraws
Using JSX to Render Several Elements at Once 24
render() {
return <p>Hello, {chance.first()}.</p>
<p>You're from {chance.country({ full: true })}.</p>;
}
And here’s the fixed code that wraps both those <p> elements inside a single <div>:
src/pages/Detail.js
render() {
return (<div>
<p>Hello, {chance.first()}.</p>
<p>You're from {chance.country({ full: true })}.</p>
</div>);
}
It doesn’t matter how many child elements (or grandchild elements, great-grandchild elements, etc)
that <div> has, as long as there is only one element being returned.
Returning more than one JSX element from your render() method is not possible and will output a great many
error messages.
www.hackingwithreact.com @twostraws
Handling Events with JSX: onClick 25
src/pages/Detail.js
render() {
return (<div>
<p>Hello, {chance.first()}.</p>
<p>You're from {chance.country({ full: true })}.</p>
<button onClick={this.buttonClicked}>Meet Someone New</button>
</div>);
}
}
• The <button> JSX element is identical to the <button> HTML element: it draws a clickable
button on the screen, in this case saying “Meet Someone New”.
• onClick={this.buttonClicked} is how you attach events to JSX elements. You shouldn’t put
quotes around it like you would have done with HTML – this is code being called, after all.
• There’s a new buttonClicked() method that writes some text to your browser’s console
window. It uses this to mean “this is my method as opposed to someone else’s.”
www.hackingwithreact.com @twostraws
Handling Events with JSX: onClick 26
Save that code and try clicking the button – you should be able to see the message being printed
out. So, that’s the first way to call methods when events occur, and I hope you’ll agree it’s easy.
The second way is where the JavaScript quirks come in. We don’t just want to write some text to the
debug console, we want to re-render our component so that a new name and place are generated.
React components have a special (if rarely used) method baked in to do just that, and it’s called
forceUpdate(). So, you might think of writing code like this:
Sadly that doesn’t work. And it doesn’t work because JavaScript gets confused what you mean by
“this”. Well, it isn’t confused, but everyone else certainly is. So while the render() method knows
that this refers to the current instance of the Detail component, the code inside the forceUpdate()
method won’t know, and you’ll get errors.
Yes, this is confusing. Yes, it’s unpleasant. And yes, you’re stuck with it. Fortunately there’s a
relatively simple solution: a special method called bind() that lets you force JavaScript to use a
definition of this you specify. That is, you literally say, “when you see ‘this’, I mean X, not your
own crazy idea of what it might be.”
To use bind() just put it after the method name you want to call, then make sure and pass in the
current value of this to make that the one used inside your method. Don’t worry if this is confusing:
this is a JavaScript problem, not a you problem.
To solve this problem once and for all, we need to call buttonClicked() using bind(this), then we
can safely call forceUpdate() from inside buttonClicked().
First things first, here’s the new button code:
src/pages/Detail.js
src/pages/Detail.js
buttonClicked() {
this.forceUpdate();
}
Save those changes, then try clicking your button – easy, huh?
One last thing before we continue – AND THIS NEEDS A BIG WARNING IN CAPITAL LETTERS
– you must be careful not to write this by accident:
www.hackingwithreact.com @twostraws
Handling Events with JSX: onClick 27
Notice how that’s missing the .bind part? It’s a mistake you will make sooner or later, so I want to
explain briefly why it’s a mistake.
The difference is this: if you write onClick={this.buttonClicked(this)} that code gets called
immediately when your page is run, not when the button is clicked. And because that method calls
forceUpdate(), it means that render() gets called again, so onClick={this.buttonClicked(this)}
gets called again, so forceUpdate() gets called again… ad infinitum – or at least until your web
browser gives up, which is probably about a thousand or so times through that loop.
So: if an event is triggered and you need to use this inside the method to handle that event, you
need to use bind() to make sure this is what you think it is, and also to ensure the code is not called
straight away.
www.hackingwithreact.com @twostraws
State vs Props in React 28
This means our Detail component gets all the functionality provided by its parent class, Re-
act.Component. It also means that it’s good practise to let React.Component do its own initialization
if it needs to, which is what super() does: it tells the parent class to go ahead and run the same
method on itself before we can continue.
Finally, the this.state property. This is undefined by default (i.e., it doesn’t exist), but you can
create it to be a dictionary of items that contain any valid ES6 data.
www.hackingwithreact.com @twostraws
State vs Props in React 29
Putting those three things together we’re going to 1) write a constructor for the Detail component
that 2) calls super() so that React.Component can do any initializing it needs to, then 3) sets
this.state to be a value containing a name and a country of our random person.
Here’s the code to do just that – put this just above the buttonClicked() method that exists right
now:
src/pages/Detail.js
constructor(props) {
super(props);
this.state = {
name: chance.first(),
country: chance.country({ full: true })
};
}
I did sneak one extra little thing in there, which is that constructor() takes the component’s props
as its only input parameter. This must get passed on with the super() so that the parent class can
act on those props as needed.
So, now that the name and country of our person are being set up front, we can adjust our render()
method to use them:
src/pages/Detail.js
render() {
return (<div>
<p>Hello, {this.state.name}.</p>
<p>You're from {this.state.country}.</p>
<button onClick={this.buttonClicked.bind(this)}>Meet Someone New</button>
</div>);
}
The resulting page might look the same, but if you click the button you’ll see that nothing happens
– the name and country being shown never change, even though it’s all working correctly. The
reason for this is because the name and country values are set up when the component is created,
not when it’s rendered, so it doesn’t matter how many times forceUpdate() gets called: the output
won’t change.
www.hackingwithreact.com @twostraws
Changing a React Component’s State with setState() 30
src/pages/Detail.js
buttonClicked() {
const newState = {
name: chance.first()
};
this.setState(newState);
}
Note that I’ve removed the call to this.forceUpdate() – it’s no longer needed. In fact, calling
forceUpdate() is only needed if React didn’t spot a very deep state change, so we won’t be using it
from now on.
That new code does exactly what we said: it creates a newState object that has a new name key
with a random name, and tells React to merge it into the component’s current state by using
this.setState(). Because state changes automatically trigger a re-render, you’ll see a new name
every time you click the button, but the country won’t change.
In the unlikely event that you are making so many changes to state and/or props that the constant
calls to render() are making your page run slowly, React has a solution: if you create a method
called shouldUpdateComponent() and return false from it, your component won’t be re-rendered.
www.hackingwithreact.com @twostraws
Changing a React Component’s State with setState() 31
To use this, either put some logic inside shouldUpdateComponent() to return either true or
false depending on your needs, or alternatively you can always make it return false then
use this.forceUpdate() to re-render as needed – that method will force a new render even if
shouldUpdateComponent() returns false.
www.hackingwithreact.com @twostraws
State and the Single Source of Truth 32
Once that’s in your code, you can use it like any other component:
Don’t kill yourself trying to avoid state. Instead, be a pragmatic programmer: go for stateless
components where possible, but state is there to be used when you really need it.
www.hackingwithreact.com @twostraws
Rendering an Array of Data with map() and JSX 33
src/pages/Detail.js
constructor(props) {
super(props);
this.state = { people };
}
Note that I’m using short-hand syntax to set the people value in this.state – if the key name is
the same as the value you want to use, you can just write it once.
Now that our state has an array of data to work with, we can loop over it using map() by modifying
our render() method like this:
www.hackingwithreact.com @twostraws
Rendering an Array of Data with map() and JSX 34
src/pages/Detail.js
render() {
return (<div>
{this.state.people.map((person, index) => (
<p>Hello, {person.name} from {person.country}!</p>
))}
</div>);
}
There are quite a few parentheses in there thanks to the way map() works, but that code block does
the following:
• For every item in the array, it gives us the item itself in person and the position of the item in
index.
• It creates a new anonymous function (that’s the => part) that receives those two things as a
parameter and will return a value of the modified data.
• It uses the input element to create some JSX based on the person.
If you save the file and look in your browser, you’ll probably see ten greeting messages in there so
it looks like everything is working. But if you open your browser’s error console you’ll see a large
warning: Each child in an array or iterator should have a unique “key” prop.
That error is pretty clear, but just in case you’re not sure what it means here goes: if you use a loop
like we’re doing here (with map()) you need to give every top-level item printed by that loop a key
attribute that identifies it uniquely. The reason for this is called reconciliation and it becomes very
important when you make more advanced apps – and can cause some really weird bugs if you don’t
understand it fully!
If you create JSX in a loop and don’t provide a key attribute for each element, React will warn you.
www.hackingwithreact.com @twostraws
Rendering an Array of Data with map() and JSX 35
<div>
<p>Hello, Jim from Australia!</p>
<p>Hello, Dave from Malaysia!</p>
<p>Hello, Charlotte from Thailand!</p>
</div>
That’s three paragraphs of text all wrapped up inside a <div> element – it’s trivial to make a
component that renders that. Now imagine your component’s state changes, and now it prints the
following:
<div>
<p>Hello, Jim from Australia!</p>
<p>Hello, Charlotte from Thailand!</p>
</div>
What happened? Well you and I can both see that “Dave from Malaysia!” got removed for whatever
reason, but React doesn’t know that – it just sees that there are two items rather than three, so as
far as React is concerned you just deleted the last item and moved the others up.
React asks for a key attribute so that it knows which item is which. If we re-wrote the previous
examples it would look like this:
<div>
<p key="1">Hello, Jim from Australia!</p>
<p key="2">Hello, Dave from Malaysia!</p>
<p key="3">Hello, Charlotte from Thailand!</p>
</div>
So when we delete Dave, React could see that numbers 1 and 3 remained and update accordingly.
Back to our Detail component with its random names and places: we can provide a key by using the
index value we are receiving from map(), like this:
www.hackingwithreact.com @twostraws
Rendering an Array of Data with map() and JSX 36
src/pages/Detail.js
render() {
return (<div>
{this.state.people.map((person, index) => (
<p key={index}>Hello, {person.name} from {person.country}!</p>
))}
</div>);
}
That works fine for now, but if you ever want to add, remove or move items, you’ll need to use a
key attribute that doesn’t change when items are moved or rearranged. Trust me, I’ve been there: if
you use the position of an item as its key when move items around, you’ll get some marvellously
weird behavior!
www.hackingwithreact.com @twostraws
Cleaning up and Preparing for a Real Project 37
1. How to install Webpack, Babel and React for development with ES6.
2. How to create a basic React component and import it into an application.
3. How to write simple JSX to render content.
4. How to use props to give a component values.
5. How to render several elements at once.
6. How to handle events such as onClick.
7. How to use state, and how it differs from props.
8. How to loop over and render data in an array.
All this will begin to come together now: we’re going to use an Ajax call to fetch data from GitHub.
Well, technically it’s Ajaj rather than Ajax, because GitHub provides JSON rather than XML, but
still: it’s our next big task. If you’ve never used Ajax before, it’s just a way to fetch data remotely
using the web browser.
In the terminal, quit Webpack Dev Server by pressing Ctrl+C and run this command:
SuperAgent is a ridiculously lightweight Ajax client with clear, simple syntax that makes it easy to
learn and use. We’re going to replace this whole “Hello, Scott from Scotland!” thing with the results
of an Ajax call to GitHub that will pull in a list of commits to the React project. This will require
making quite a few changes, but it’s almost all stuff you’ve seen before.
Note: when SuperAgent has finished installing, make sure you run webpack-dev-server again.
First, find these lines in your constructor:
www.hackingwithreact.com @twostraws
Cleaning up and Preparing for a Real Project 38
src/pages/Detail.js
this.state = { people };
…then delete them all. We don’t need people any more. While you’re deleting stuff, go ahead and
remove the import Chance from 'chance' line and the whole buttonClicked() method too; these
aren’t needed right now. Don’t worry: all that stuff you learned will prove useful in an upcoming
chapter, its just that for now we don’t need it.
Instead, we’re going to create some very simple initial state: an empty array of commits. This will
be filled by SuperAgent when its Ajax call completes. So, where those lines in your constructor were
just a moment ago, put this instead:
src/pages/Detail.js
this.state = { commits: [] };
As for the render() method, we’re going to change the variable names but otherwise just print out
static data – we’ll fill in the specifics soon enough, don’t worry. Change it to be this:
src/pages/Detail.js
render() {
return (<div>
{this.state.commits.map((commit, index) => (
<p key={index}>Some commit data here.</p>
))}
</div>);
}
Just to make sure you’re following along, here’s how your component should look right now:
www.hackingwithreact.com @twostraws
Cleaning up and Preparing for a Real Project 39
src/pages/Detail.js
this.state = { commits: [] };
}
render() {
return (<div>
{this.state.commits.map((commit, index) => (
<p key={index}>Some commit data here.</p>
))}
</div>);
}
}
Once you save that file, your web page will go blank. This is because the commits array starts empty
and never gets filled. Let’s fix that now…
www.hackingwithreact.com @twostraws
Fetching Ajax Data from GitHub using SuperAgent 40
src/pages/Detail.js
Note: you can call SuperAgent whatever you want in your code, and their own examples usually
alias it as request rather than ajax. I find ajax easy to remember, which is why I use it.
Now, we want our Ajax call to run when our page loads, and React has a special method that gets
called at just the right time: componentWillMount(). As you can probably guess from the name, this
method gets called on a component just before it’s rendered for the very first time. This makes it a
great place for us to kick off our Ajax request.
Add this method to your component:
src/pages/Detail.js
componentWillMount() {
ajax.get('https://api.github.com/repos/facebook/react/commits')
.end((error, response) => {
if (!error && response) {
this.setState({ commits: response.body });
} else {
console.log('There was an error fetching from GitHub', error);
}
}
);
}
1. componentWillMount() is the name of the method, and needs to be named exactly this in
order for React to call it.
www.hackingwithreact.com @twostraws
Fetching Ajax Data from GitHub using SuperAgent 41
There are two more things you need to know in order to understand that code. First: all SuperAgent
calls are asynchronous, which means your code doesn’t just freeze while SuperAgent waits for
GitHub to respond. Instead, other code executes, and SuperAgent will only call your anonymous
function when it has finished getting GitHub’s response.
The second thing to know is that response.body is a bit of SuperAgent magic: it has detected that
GitHub has responded with the content type “application/json” and automatically converts GitHub’s
response into JavaScript objects. That’s why we can send response.body straight into our state: it’s
already an array of objects ready to use.
When you save your page now, you’ll see “Some commit data here” printed out lots of times in your
browser. Each of those is the result of one commit to the Facebook GitHub repository, but we’re not
doing anything with each commit just yet – our JSX is static.
Our app now displays “Some commit data here” 30 times, but only because we haven’t told React what data we
want it to show.
www.hackingwithreact.com @twostraws
Converting GitHub’s JSON into Meaningful JSX 42
src/pages/Detail.js
console.dir(response.body);
Once that’s done, save and reload the page in your browser, then look in the error log window and
you should see “Array [30]” or similar in there. Using console.dir() prints a navigable tree in the
log, so you should be able to click an arrow next to “Array [30]” to see the individual objects inside
it.
Use your web browser’s console area to explore the GitHub JSON in a tree structure.
Each of the objects you see is an individual React code commit to GitHub, and each one should have
another arrow next to it that you can fold out to see what’s inside the commit. Things that stand
out to me as being interesting are:
www.hackingwithreact.com @twostraws
Converting GitHub’s JSON into Meaningful JSX 43
Warning: GitHub can change its API in the future, so these fields may not apply when you try it
yourself. So, look through the result of console.dir() and find something that interests you!
What we’re going to do is print the name of the author in bold, then the full text of their commit.
I’m going to make the commit text clickable using the GitHub URL for the commit.
In the perfect world, the JSX to make this happen is simple:
(<p key={index}>
<strong>{commit.author.login}</strong>:
<a href={commit.html_url}>{commit.commit.message}</a>.
</p>)
(Note 1: we need to use commit.commit.message and not commit.message because the message is
inside an object that is itself called commit. Note 2: it is stylistically preferred to add parentheses
around JSX when it contains multiple lines.)
Sadly, if you use that code there’s a chance you’ll get an error. It’s not guaranteed because obviously
the list of commits you see depends on what commits have happened recently, but sometimes there
is nothing in the author field – that gets set to null. So, trying to use commit.author.login will fail
because commit.author doesn’t exist.
There are a few ways to solve this. First, we could clean the data when it arrived in from the Ajax
call: if a commit doesn’t have an author just skip over it. Second, we could use a ternary expression
to check the for the existence of an author and provide a meaningful default if it doesn’t exist, like
this:
(<p key={index}>
<strong>{commit.author ? commit.author.login : 'Anonymous'}</strong>:
<a href={commit.html_url}>{commit.commit.message}</a>.
</p>)
That’s a simple enough solution, but what happens if the commit HTML URL is missing, or the
commit message is missing? You end up with ternary expressions scattered everywhere, which is
ugly.
Instead, there is a third option: calculate any fields up front. This means using slightly different
syntax: we need open and close braces with map(), and our code needs to return a value using the
return keyword.
Using this technique, here’s the new render() method for the Detail component:
www.hackingwithreact.com @twostraws
Converting GitHub’s JSON into Meaningful JSX 44
src/pages/Detail.js
render() {
return (<div>
{this.state.commits.map((commit, index) => {
const author = commit.author ? commit.author.login : 'Anonymous';
This revised code creates a new author constant that is set to either the name of the author or
Anonymous depending on whether an author was provided. It still uses a ternary expression, but it
separates the calculation of the values from the rendering, which makes it easier to read.
Our project so far: all recent React commits are shown along with their author, plus a link to more information.
www.hackingwithreact.com @twostraws
Time for a Task: Reading from Three Feeds 45
• https://api.github.com/repos/facebook/react/commits
• https://api.github.com/repos/facebook/react/forks
• https://api.github.com/repos/facebook/react/pulls
You should examine the data they return so you can craft appropriate JSX.
Important note: we have already covered all the techniques required for you to be able to do this
yourself. I’m going to walk through it with you, but this is a great place to test yourself to make sure
you’ve understood what happened so far.
Still here? Here are some hints:
• Your render() method can call other methods to do rendering rather than try to do it all itself.
• So, you could have a renderCommits() method, a renderForks() method and a render-
Pulls() method, then have your main render() method call one of them.
• Depending on which fields you find interesting, you might find you can use one method for
them all, or two of them; it’s down to you.
• To keep things simple, just go ahead and call all through API endpoints in componentWill-
Mount() and store the results in three separate arrays in state. Remember, setState() merges
the new data with existing data.
• The current active view mode (commits, forks, or pulls) should be stored in state, and that
state can be changed by clicking one of three buttons.
With all that, you should be able to create a very simple solution. If you want to refactor it later (i.e.,
to rewrite it to be simpler code) you can do, but for now just go with the easiest solution so you can
be sure you fully understand what you’ve learned so far.
www.hackingwithreact.com @twostraws
How to Upgrade Our App to Read Three Feeds 46
src/pages/Detail.js
this.state = {
mode: 'commits',
commits: [],
forks: [],
pulls: []
};
The second thing to do is upgrade the componentWillMount() so that it makes three GitHub calls.
For now – the MVP, as it were – we’ll just duplicate the code three times. Don’t worry, we’ll clean
this up soon.
Here’s the new componentWillMount() method:
src/pages/Detail.js
componentWillMount() {
ajax.get('https://api.github.com/repos/facebook/react/commits')
.end((error, response) => {
if (!error && response) {
this.setState({ commits: response.body });
} else {
console.log('Error fetching commits', error);
}
}
);
ajax.get('https://api.github.com/repos/facebook/react/forks')
www.hackingwithreact.com @twostraws
How to Upgrade Our App to Read Three Feeds 47
ajax.get('https://api.github.com/repos/facebook/react/pulls')
.end((error, response) => {
if (!error && response) {
this.setState({ pulls: response.body });
} else {
console.log('Error fetching pulls', error);
}
}
);
}
Next up, we need three rendering methods so that each view type shows relevant information. I’ve
called these renderCommits(), renderForks() and renderPulls():
src/pages/Detail.js
renderCommits() {
return this.state.commits.map((commit, index) => {
const author = commit.author ? commit.author.login : 'Anonymous';
renderForks() {
return this.state.forks.map((fork, index) => {
const owner = fork.owner ? fork.owner.login : 'Anonymous';
www.hackingwithreact.com @twostraws
How to Upgrade Our App to Read Three Feeds 48
</p>);
});
}
renderPulls() {
return this.state.pulls.map((pull, index) => {
const user = pull.user ? pull.user.login : 'Anonymous';
Note: you will probably need to adjust the renderForks() method so that the link sits on the same
line as the “forked to” otherwise React will not put any space between the words.
That isolates the rendering for each view type in its own method, which means we now just need to
make render() choose which one to show. I’m using the mode key in the component state to decide
which to show, and I’ll let it have three values: “commits”, “forks” and “pulls”.
With that in mind, here’s how render() should look:
src/pages/Detail.js
render() {
let content;
return (<div>
<button onClick={this.showCommits.bind(this)}>Show Commits</button>
<button onClick={this.showForks.bind(this)}>Show Forks</button>
<button onClick={this.showPulls.bind(this)}>Show Pulls</button>
{content}
</div>);
}
www.hackingwithreact.com @twostraws
How to Upgrade Our App to Read Three Feeds 49
You can see three buttons at the end of that method that call three as-yet-undefined methods when
they are clicked: showCommits(), showForks() and showPulls(). All these need to do is change the
mode state key to have the component refresh with different data:
src/pages/Detail.js
showCommits() {
this.setState({ mode: 'commits' });
}
showForks() {
this.setState({ mode: 'forks' });
}
showPulls() {
this.setState({ mode: 'pulls' });
}
Remember, changing the state or props of a component causes it to re-render, which means clicking
those buttons will update our output as expected.
Before I move on, here’s a complete copy of Detail.js at this point in the project. If you’re having
problems, you should be able to compare my version against yours and see what’s missing:
src/pages/Detail.js
this.state = {
mode: 'commits',
commits: [],
forks: [],
pulls: []
};
}
componentWillMount() {
ajax.get('https://api.github.com/repos/facebook/react/commits')
.end((error, response) => {
www.hackingwithreact.com @twostraws
How to Upgrade Our App to Read Three Feeds 50
ajax.get('https://api.github.com/repos/facebook/react/forks')
.end((error, response) => {
if (!error && response) {
this.setState({ forks: response.body });
} else {
console.log('Error fetching forks', error);
}
}
);
ajax.get('https://api.github.com/repos/facebook/react/pulls')
.end((error, response) => {
if (!error && response) {
this.setState({ pulls: response.body });
} else {
console.log('Error fetching pulls', error);
}
}
);
}
showCommits() {
this.setState({ mode: 'commits' });
}
showForks() {
this.setState({ mode: 'forks' });
}
showPulls() {
this.setState({ mode: 'pulls' });
}
renderCommits() {
www.hackingwithreact.com @twostraws
How to Upgrade Our App to Read Three Feeds 51
renderForks() {
return this.state.forks.map((fork, index) => {
const owner = fork.owner ? fork.owner.login : 'Anonymous';
renderPulls() {
return this.state.pulls.map((pull, index) => {
const user = pull.user ? pull.user.login : 'Anonymous';
render() {
let content;
www.hackingwithreact.com @twostraws
How to Upgrade Our App to Read Three Feeds 52
return (<div>
<button onClick={this.showCommits.bind(this)}>Show Commits</button>
<button onClick={this.showForks.bind(this)}>Show Forks</button>
<button onClick={this.showPulls.bind(this)}>Show Pulls</button>
{content}
</div>);
}
}
As you can see, there are no great surprises in there – it’s just taking what we already have and
repeating it three times over.
www.hackingwithreact.com @twostraws
Refactoring our Ajax Code: Don’t Repeat Yourself 53
• ajax.get('https://api.github.com/repos/facebook/react/commits')
• this.setState({ commits: response.body })
• console.log('There was an error fetching commits from GitHub', error);
More importantly, those three lines all vary only by three words, and the word is the same each
time: commits, commits, commits; forks, forks, forks; pulls, pulls, pulls. This is ripe for refactoring:
we could create a method that accepts a string as its only parameter, e.g. “commits”, then puts it into
those three places. We can then call that method three times in componentWillMount().
To make this work we need some new ES6 syntax: string interpolation and computed property
names. I’ll show you the code first then explain the interesting bits – please add this method to the
Detail component:
www.hackingwithreact.com @twostraws
Refactoring our Ajax Code: Don’t Repeat Yourself 54
src/pages/Detail.js
fetchFeed(type) {
ajax.get(`https://api.github.com/repos/facebook/react/${type}`)
.end((error, response) => {
if (!error && response) {
this.setState({ [type]: response.body });
} else {
console.log(`Error fetching ${type}`, error);
}
}
);
}
So, it’s a method called fetchFeed() and it takes a single parameter called type. To place that into
the ajax.get() URL I’ve used ES6 string interpolation: the URL is now wrapped in backticks (those
funny angled quotes that usually sit share a key with ∼) rather than single quotes, and when you do
that you can place variables (including other expressions) right inside the string. When the compiler
sees ${type} it substitutes the contents of the type parameter at that point. The same technique is
used in the console.log() statement.
The second ES6 feature in there is called computed property names, and you see it in the call to
this.setState(). Take a look at this code:
Is that saying a) “put response.body in my object using the name type”, or b) “put response.body
in my object using the name commits because that’s what the type parameter is set to”?
The answer is a), and there were some ugly hacks you could do to work around that. With ES6 you
can now write [type], which is a computed property name, and effectively tells the computer you
mean b).
With the new fetchFeed() method in place, we just need to call it three times when the component
mounts. Modify your componentWillMount() method to rip out all that Ajax code and replace it
with this:
www.hackingwithreact.com @twostraws
Refactoring our Ajax Code: Don’t Repeat Yourself 55
src/pages/Detail.js
componentWillMount() {
this.fetchFeed('commits');
this.fetchFeed('forks');
this.fetchFeed('pulls');
}
www.hackingwithreact.com @twostraws
Refactoring our State Code: Passing Parameters in onClick 56
src/pages/Detail.js
return (<div>
<button onClick={this.selectMode.bind(this, 'commits')}>Show Commits</button>
<button onClick={this.selectMode.bind(this, 'forks')}>Show Forks</button>
<button onClick={this.selectMode.bind(this, 'pulls')}>Show Pulls</button>
{content}
</div>);
That makes all three buttons call the same method, so it’s now just a matter of writing the
selectMode() method so that it accepts a parameter and uses it to set the mode state:
src/pages/Detail.js
selectMode(mode) {
this.setState({ mode });
}
Note: you don’t need to use the new ES6 computed property name syntax here, because you’ve
always been able to use variables as values. In fact, because the key and value are the same, we can
just write mode.
With selectMode() in place, you can go ahead and delete showCommits(), showForks(), and
showPulls().
That code works, and it works well. But we could rewrite it slightly differently, and I’m going to
show it to you because it’s the kind of thing you’ll find in real code, not because I’m saying I favor
www.hackingwithreact.com @twostraws
Refactoring our State Code: Passing Parameters in onClick 57
one approach over the other. There are two ways of doing it, and two camps of people who each are
convinced their way is the One True Way, but again I suggest you try to be pragmatic.
IMPORTANT WARNING: I am going to show you how this looks just so you’re aware of it. You
should keep using your existing code rather than switch to this alternative.
The other way we could write these onClick is by storing data in the buttons that describe what
they should do. The selectMode() method can then read that data and act appropriately. To take
this approach, we would need to modify the render() method to this:
return (<div>
<button onClick={this.selectMode.bind(this)} data-mode="commits">
Show Commits
</button>
{content}
</div>);
(Note: I split the <button> elements onto multiple lines to make them easier to read; you can write
them on one line if you prefer.)
As you can see, that no longer passes a parameter string to the selectMode() method. Instead,
the strings are stored inside data-mode parameters. To make selectMode() work with this relies
on a JavaScript implementation detail: all event handlers are automatically passed an event object
describing what happened. We haven’t been using this so it’s been silently ignored. But in code that
uses this data-mode attribute approach we would – here’s how the selectMode() method would
need to look:
selectMode(event) {
this.setState({ mode: event.currentTarget.dataset.mode });
}
www.hackingwithreact.com @twostraws
Refactoring our State Code: Passing Parameters in onClick 58
As you can see, to read the data-mode attribute of whichever button was clicked, we just read the
dataset.mode property of the event’s currentTarget – that will automatically be the clicked button.
There are good reasons to use both of these ways of calling methods. Explicitly passing parameters
makes your code a bit easier to read because you can see exactly what is being sent and what is being
received, but having methods pull data from the event can be helpful to reduce code duplication.
Again, be pragmatic!
REMINDER OF IMPORTANT WARNING: Code much later in this book relies on you passing a
string parameter to selectMode() rather than using the data-mode attribute approach.
That’s enough refactoring for now. If it were my own code, I’d probably try to harmonize the various
rendering methods a little, but it’s not something we can do here because you’ve probably chosen
different JSON fields to me. Still, consider it an exercise: can you get down from four rendering
methods to three, two or even one? You might need to clean the JSON before you use it for your
component’s state.
www.hackingwithreact.com @twostraws
Introducing React Router 59
src/index.js
I’d like you to add two more imports after the first two you have:
www.hackingwithreact.com @twostraws
Introducing React Router 60
src/index.js
The first import brings three components, of which we’ll be using two immediately and the other
shortly. Router is React Router itself, which takes a list of URLs and React components and puts the
two together. Route is one individual URL mapping, e.g. the URL detail and our Detail component.
IndexRoute is used as a default route; we’ll get onto that soon.
The second import brings in a great deal of complexity, and chances are you don’t want to know
about it. In short, React Router needs a smart way to handle history. This is partly done through
that # sign (known as a hash history because # is called “hash” to people who don’t play music),
and partly through special query keys in your URLs. These query keys aren’t needed in our example
and just make things look a bit ugly, so we’ll be taking them out. But to take them out, we need this
import line as you’ll see in a moment.
With those new imports, it’s time to turn to the main rendering of our app. Right now we just render
a Detail component, but we need to change that so we use React Router instead. Here’s how it looks
in its most basic form:
src/index.js
ReactDOM.render(
<Router>
<Route path="/" component={ Detail } />
</Router>,
document.getElementById('app')
);
So, rather than rendering our Detail component directly, we now render Router, which in turn
creates the appropriate child component for the URL that gets matched. Right now we specify only
one URL, /, and tell React Router to load our Detail component.
If you save those changes, try refreshing your browser. All being well, http://localhost:8080/ should
update to become something like http://localhost:8080/#/?_k=7uv5b6. That’s the hash history in
action: that ?_k= part is a unique key used to track state between locations, but we really don’t need
it so we’ll remove it.
www.hackingwithreact.com @twostraws
Introducing React Router 61
A basic install of React Router shows URLs with random numbers in the query string.
At the same time, we’re also going to add what might seem like a bit of a hack, but I’m afraid it’s a
required hack until React Router solves it permanently. You see, when you navigate from /#/someurl
to /#/someotherurl, you’re not actually moving anywhere – React Router just unloads the previous
components and loads the new one in its place. This causes a problem with scrolling, because if
the user had scrolled half way down the page before changing URLs, they would remain half way
scrolled down the page for the new component.
So, the hack is this: whenever the React Router updates, we tell the browser to scroll back to the top
of the document, just as it would if we were changing pages the old-fashioned way.
We can make both these changes at the same time. Replace your current <Router> line with this
one:
src/index.js
The history part is what removes the ?_k= mess from our URLs. The onUpdate part is what makes
sure the user’s scroll position resets when they move between components.
With those changes saved, you should be able to navigate to http://localhost:8080/ and find
yourself on http://localhost:8080/#/, which is what we want. Again, removing the # sign
requires server configuration that we aren’t going to do here. If you’d like to read more about this,
here’s the React Router documentation page you’re looking for.
www.hackingwithreact.com @twostraws
How to Add a New Route to React Router 62
src/pages/List.js
We’re going to make it so that going to the homepage loads our List component, and going to /react
loads our Detail component. To make this happen we need to add a new route, then move the existing
one.
As a reminder, here is your current index.js file
src/index.js
www.hackingwithreact.com @twostraws
How to Add a New Route to React Router 63
12 </Router>,
13 document.getElementById('app')
14 );
Please add an import for the new List component you made a moment ago. If you’re not sure, it
should look like this:
src/index.js
Now we need to move our existing route so that it handles /react and make a new route to handle
/, like this:
src/index.js
That’s it! You should be able to point your web browser at http://localhost:8080/#/ to see “This is
the list page”, then point it at http://localhost:8080/#/react to see our old page.
That wasn’t hard, right? Right. But neither was it very useful: we need a way for users to be able to
select a GitHub repository to view, which means upgrading our List page…
www.hackingwithreact.com @twostraws
Creating a Link Between Pages in React Router 64
src/pages/List.js
www.hackingwithreact.com @twostraws
Creating a Link Between Pages in React Router 65
16
17 export default List;
Even though we’re linking to /react, that gets silently rewritten by React Router to be /#/react,
which means our current URLs all carry on working correctly. It also means that if you change your
server configuration so the whole /#/ isn’t needed any more, those links will automatically update.
The new List page uses a React Router Link component to navigate to our Detail page.
www.hackingwithreact.com @twostraws
Making Custom URLs with React Router Params 66
src/index.js
<Route path="/" component={ List } />
<Route path="/react" component={ Detail } />
You might very well think we just need to extend that like so:
That’s certainly a possibility, but it’s neither flexible or scalable. Wouldn’t it be much better if we
could write any link like /detail/??? and have our Detail component figure out what that means?
Sure it would. And fortunately React Router makes it easy – in fact it’s just a matter of rewriting
your routes to this:
src/index.js
<Route path="/" component={ List } />
<Route path="/detail/:repo" component={ Detail } />
Yes, that’s it. Just by writing :repo in the URL, React Router will automatically pull out whatever
text comes in that part of the URL, then pass it to the Detail component to act on. Sure, we still need
to actually do something with the repository name, but it means the Detail component will now
work for /detail/react, /detail/react-native and so on.
Given how easy that step was, you’re probably imagining there’s lots of work to do in the Detail
component. Well, you’d be wrong: we have just to change just one part of one line in order to make
it work. Isn’t React Router clever?
In Detail.js look for this line inside the fetchFeed() method:
www.hackingwithreact.com @twostraws
Making Custom URLs with React Router Params 67
src/pages/Detail.js
ajax.get(`https://api.github.com/repos/facebook/react/${type}`)
If you remember, that uses ES6 string interpolation so that the URL gets written as …/react/commits,
…/react/pulls, etc. Thanks to the magic of React Router, we can use the exact same technique with
the name of the repository too. We used :repo inside our route, and React Router will automatically
make that available to the Detail component as this.props.params.repo.
So, replace that existing ajax.get() call with this:
src/pages/Detail.js
That now does a triple interpolation: once for the :repo part of our URL, and again for the view
mode that’s currently selected, i.e. commits, forks and pulls. I added a third one for baseURL to avoid
the line getting too long to read easily.
The final step is to modify List.js so that it points to more than one repository. Update its render()
method to this:
src/pages/List.js
1 render() {
2 return (
3 <div>
4 <p>Please choose a repository from the list below.</p>
5 <ul>
6 <li><Link to="/detail/react">React</Link></li>
7 <li><Link to="/detail/react-native">React Native</Link></li>
8 <li><Link to="/detail/jest">Jest</Link></li>
9 </ul>
10 </div>
11 );
12 }
Now save all your work, and go to http://localhost:8080/ in your browser. You should see three
links to choose from, each showing different GitHub repositories. You should also be able to use
your browser’s back button to return to the list and choose a different repository.
www.hackingwithreact.com @twostraws
Making Custom URLs with React Router Params 68
The new List page shows multiple repositories for the user to choose from, and all three point to the same Detail
page.
www.hackingwithreact.com @twostraws
Adding a Root Route Using React Router and IndexRoute 69
src/pages/App.js
The only really interesting part of that code is {this.props.children}. All it means is “render my
child components here,” so all this new App component does is add a heading above whatever page
component is rendered beneath it – i.e., our List and Detail components.
The page itself was simple enough, but now we need to update our index.js file, and this is a little
trickier because you need to learn two new concepts:
www.hackingwithreact.com @twostraws
Adding a Root Route Using React Router and IndexRoute 70
• Any route can have child routes inside it, and these build upon the parent route.
• If you want a child route to be used as the default when no other child matches, you use a
special route called <IndexRoute>.
Neither of those concepts will make any sense without a practical example, so I want to modify
index.js immediately so you can see both of those new things in action. Here’s the new code:
src/index.js
You’ll see I’ve added an import for the new App component we created a few moments ago, but the
main difference is this:
That’s the React Router structure for those two new concepts I just introduced. First, notice the
new <IndexRoute> component: it means “if my parent route was matched but none of my siblings
matched, render me.” Second, notice how <Route path="/" component={ App }> actually contains
two things inside it: the IndexRoute and another Route.
www.hackingwithreact.com @twostraws
Adding a Root Route Using React Router and IndexRoute 71
Before continuing I want to explain both of these concepts in a bit more depth because they
are both easy to get wrong. If you think you understand them, feel free to skip ahead.
The <IndexRoute> is important, and to explain what it does let’s pretend it isn’t there. If the user
navigates to http://localhost:8080/, it would match the App route (path=”/”), but it wouldn’t match
the Detail route (path=”detail/:repo”), so all they would see is “Unofficial GitHub Browser v0.1” in
large text.
If we wanted to use a regular <Route> in place of the <IndexRoute>, what path would it have? You
might guess something like <Route path="" component={ List } />, but that just doesn’t work.
This is what <IndexRoute> gives us: a default child for the App component that will be used when
no other route matches.
The second concept is having one route inside another, which is called a nested route. When you nest
routes – i.e., put one route inside another - they build up as saw earlier using this.props.children.
Right now we have a very simple structure: our App component at the base, then either List or
Detail depending on what was matched. But it’s possible to have routes inside routes inside routes
inside… well, you get the point. If D is inside C, which is inside B, which is inside A, then all four
components get rendered, adding their own parts to your user interface as needed.
It’s because of this nesting that child routes don’t need to start with a / in their path. If you look
back to our routes list from 20 minutes ago we used <Route path="/detail/:repo" component={
Detail } /> but now we don’t need to start the path with a / because we already matched that part
of the URL in the parent route.
Now, for the sake of completeness I should add that you can start child route paths with a / if you
want to, but it has a very special meaning: it lets you specify an exact URL for a child path (ignoring
whatever its parents match) while keeping the nested component rendering. It’s clever and I love
that it’s available, but at this stage in your React career I’d leave it out if I were you.
www.hackingwithreact.com @twostraws
Cleaning up Our Routes and Preparing for the Next Step 72
src/routes.js
1 import React from 'react';
2 import { Route, IndexRoute } from 'react-router';
3
4 import App from './pages/App';
5 import List from './pages/List';
6 import Detail from './pages/Detail';
7
8 const routes = (
9 <Route path="/" component={ App }>
10 <IndexRoute component={ List } />
11 <Route path="detail/:repo" component={ Detail } />
12 </Route>
13 );
14
15 export default routes;
That imports only what it needs, then creates a constant containing the route configuration for our
app, and exports that constant so that others can use it.
What remains in index.js is the basic router configuration and the main call to ReactDOM.render().
Over time, as your application grows, you’ll probably add more to this file, but trust me on this:
you’ll definitely fare better if you keep your route configuration out of your main index.js file.
Here’s the new code for index.js:
www.hackingwithreact.com @twostraws
Cleaning up Our Routes and Preparing for the Next Step 73
src/index.js
With that little clean up complete it’s time for your first major challenge.
www.hackingwithreact.com @twostraws
Time for a Test: Clickable Usernames 74
1. How to install Webpack, Babel and React for development with ES6.
2. How to create a basic React component and import it into an application.
3. How to write simple JSX to render content.
4. How to use props to give a component values.
5. How to render several elements at once.
6. How to handle events such as onClick.
7. How to use state, and how it differs from props.
8. How to loop over and render data in an array.
9. How to fetch data from GitHub using SuperAgent and Ajax.
10. How to use string interpolation and computed property names.
11. How to pass parameters using onClick.
12. How to create routes using React Router.
13. How to create links between pages using <Link>.
14. How to render default content using <IndexRoute>.
15. How to store your React Router routes separately from your code.
You should be very proud of your progress so far – you’ve learned a lot! So, it’s time to put all your
new skills to the test with a task. Your job is to:
• Create a new page called User, stored in User.js and available at the URL /user/xxxx, where
xxxx is the name of a user to view.
• Make it fetch the feed https://api.github.com/users/xxxx/events, again where xxxx is the
name of a user.
• Update the existing Detail component so that all usernames are clickable, showing the correct
User page for that user.
www.hackingwithreact.com @twostraws
Time for a Test: Clickable Usernames 75
Trust me on this: you know everything required to make that happen. It’s just a matter of using
what you know in different ways, inspecting the JSON from GitHub to select what you want to
show, then 10 to 20 minutes of coding depending on how confident you feel.
All set? Go for it!
Still here? Here are some hints:
• Look at an example GitHub user in your web browser so you can see exactly what you’re
working with – https://api.github.com/users/jimfb/events is as good an example as any.
• You’ll need to look at the detail/:repo route in routes.js for inspiration how to handle any
username.
• Make sure you use <Link> components to direct users to your new page.
www.hackingwithreact.com @twostraws
Making Usernames Clickable: My Solution 76
src/pages/User.js
www.hackingwithreact.com @twostraws
Making Usernames Clickable: My Solution 77
28 </div>);
29 }
30 }
31
32 export default User;
That is just a cut-down version of the Detail component right now. To keep things simple, I’ve moved
the Ajax call back to componentWillMount() because we’re only fetching one type of event here,
and the render() method doesn’t do anything yet – we’ll get onto that soon enough.
Before, though, I want to update routes.js so that it sends users towards this new component ready
to load. You should notice that I’ve used this.props.params.user twice in the code above, which
means you should be able to figure out what the new route should be in routes.js:
src/routes.js
Note: you will need to add import User from './pages/User'; to your list of imports in order for
that to work.
The last thing we do before starting the work of making the User page render correctly is to update
the Detail component with links on all the usernames. So, open up Detail.js for editing, and you can
start by adding this to the list of imports:
src/pages/Detail.js
You can now add a <Link> component in all three places usernames appear: renderCommits(),
renderForks(), and renderPulls(). This is a pretty trivial change, but just for reference here’s
how I updated mine:
src/pages/Detail.js
renderCommits() {
return this.state.commits.map((commit, index) => {
const author = commit.author ? commit.author.login : 'Anonymous';
www.hackingwithreact.com @twostraws
Making Usernames Clickable: My Solution 78
renderForks() {
return this.state.forks.map((fork, index) => {
const owner = fork.owner ? fork.owner.login : 'Anonymous';
renderPulls() {
return this.state.pulls.map((pull, index) => {
const user = pull.user ? pull.user.login : 'Anonymous';
We now have a route to our new page, several links to it from the Detail component, plus a very
basic implementation in User.js. Hopefully you had a look through an example user feed to see what
data is available, and have some ideas of what you want to do.
If you want to go all out, you might want to consider having more than one render() method for
the User component, just like with the Detail component, so that you can expose lots of interesting
information. Here, though, I’ll keep it simple, and you can add more info in your own time. We’re
just going to use the event type (e.g. “PushEvent”), the repository name, and the creation date.
The basic code I took from Detail does nearly all the work. In fact, all that’s left is to write the
render() method and we’re done. Rather than use lots of paragraphs of text, I decided to show this
page as a list – you’re welcome to be more creative! Here’s my render() method in the User page:
www.hackingwithreact.com @twostraws
Making Usernames Clickable: My Solution 79
src/pages/User.js
render() {
return <ul>
{this.state.events.map((event, index) => {
const eventType = event.type;
const repoName = event.repo.name;
const creationDate = event.created_at;
As with printing forks from earlier, you might find you need to put at {creationDate} part on the
same line as the {eventType} to avoid missing whitespace.
That’s it! You should now be able to start at http://localhost:8080/#/, choose a repository, click a
button to select whether you want to see commits, forks, or pulls, then click a username to view
their recent history – good job!
www.hackingwithreact.com @twostraws
Time for some Basic User Interface Polish 80
dist/style.css
1 body {
2 line-height: 1.428571429;
3 font-family: sans-serif;
4 }
5
6 h1 {
7 font-weight: 100;
8 font-size: 250%;
9 margin-bottom: 0;
10 color: #0275d8;
11 }
12
13 a {
14 color: #0275d8;
15 text-decoration: none;
16 }
17
18 a:hover {
19 text-decoration: underline;
20 }
21
22 a.active {
23 color: black;
24 }
25
26 button {
27 padding: 5px 20px;
www.hackingwithreact.com @twostraws
Time for some Basic User Interface Polish 81
28 background-color: white;
29 margin: 10px;
30 border: 1px solid #aaaaaa;
31 border-radius: 5px;
32 outline-width: 0;
33 }
34
35 button:active {
36 background-color: #dcdcdc;
37 }
To make that work you’ll need to modify your index.html page so that it pulls in that stylesheet.
Here’s the updated version:
dist/index.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <link rel="stylesheet" href="style.css" />
5 </head>
6 <body>
7 <div id="app"></div>
8 <script src="bundle.js"></script>
9 </body>
10 </html>
If you want to add some more CSS please go ahead, but you might find it better to read the next
chapter first otherwise you might break something!
www.hackingwithreact.com @twostraws
Adding React Router Breadcrumbs with Link and IndexLink 82
You’ve seen the <Link> component before, but now we’re adding <IndexLink> into the mix. You’ll
see why in just a moment!
We’re going to start by adding breadcrumbs to the Detail component. Modify the last part of its
render() method to this:
src/pages/Detail.js
return (<div>
<p>You are here: <IndexLink to="/" activeClassName="active">Home</IndexLink>
> {this.props.params.repo}</p>
Only the “You are here” line is new, but immediately you’ll see I’m using that new <IndexLink>
component. If you save your page and navigate to a repository you should see something like “You
are here: Home > react” in the breadcrumb bar, and the “Home” text is a link back to the home page.
How come it’s an <IndexLink> rather than a regular <Link>, then?
www.hackingwithreact.com @twostraws
Adding React Router Breadcrumbs with Link and IndexLink 83
dist/style.css
a.active {
color: black;
}
src/pages/Detail.js
So, that CSS specifies a style for <a> elements that have the class name active. Then the <IndexLink>
component has an activeClassName attribute set to active. This means that when React detects this
link is currently being viewed, it will automatically apply the active class to the link.
But there’s a problem: all our URLs start with / because it’s right there at the base of our routes.
When configuring our routes we created a special <IndexRoute> to handle this situation, but a
regular <Link> component doesn’t take that into account. If you want to say “consider / active only
when we’re on the List page”, you need to use <IndexLink> to match the link to the <IndexRoute>
we defined.
The simple rule is this: if you’re pointing to the index route of your site, you need to use an index
link.
Now that you know the difference between <Link> and <IndexLink> we just need to add bread-
crumbs to the List and User components.
The List component already has a message saying “Please choose a repository from the list below”,
so all you need to do is add the breadcrumbs before that:
www.hackingwithreact.com @twostraws
Adding React Router Breadcrumbs with Link and IndexLink 84
src/pages/List.js
The User component is a little more difficult because its root JSX element is <ul>. We need to wrap
that in a <div> so that we can include the breadcrumbs in its output. For the avoidance of doubt,
the new render() method should look like this:
src/pages/User.js
render() {
return (<div>
<p>You are here:
<IndexLink to="/" activeClassName="active">Home</IndexLink>
> {this.props.params.user}</p>
<ul>
{this.state.events.map((event, index) => {
const eventType = event.type;
const repoName = event.repo.name;
const creationDate = event.created_at;
www.hackingwithreact.com @twostraws
The First 80% is Done: Now What? 85
www.hackingwithreact.com @twostraws
The First 80% is Done: Now What? 86
(Before you send me flames: yes, I know Mocha is faster, but Jest is easier and that counts for a lot
in a beginners’ tutorial. And yes, I am fully aware that Jest without mocking is less than ideal, but
trust me: I spent a long time weighing up all the options.)
All set? Let’s do this!
www.hackingwithreact.com @twostraws
How to Configure Jest to Test React and ES6 87
As before, you’ll need to press Ctrl+C to quit Webpack Dev Server before you run that, but this time
I don’t want you to restart Webpack Dev Server when the npm command finishes. We’re done with
it – it’s all testing now!
We’re going to start our tests in a new subdirectory of our project. We already have dist for our
finished files, node_modules for all the NPM things we use, and src for our ES6 source code. But I
want you to create a fourth folder now:
mkdir __tests__
That’s two underscores, the word “tests”, then two more underscores. Please name it exactly that
otherwise your tests will not work. With that done, we’re going to modify our package.json file so
that it knows to call Jest when we ask it to run tests. Open your package.json file now and you’ll
see a line like this:
package.json
package.json
While you’re there, we need to insert some configuration settings to enable Jest to work with React
and ES6. Look for "devDependencies" and put this directly before it:
www.hackingwithreact.com @twostraws
How to Configure Jest to Test React and ES6 88
package.json
"jest": {
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
"unmockedModulePathPatterns": [
"<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom",
"<rootDir>/node_modules/react-addons-test-utils",
"<rootDir>/node_modules/fbjs"
]
},
package.json
1 {
2 "name": "hwr",
3 "version": "1.0.0",
4 "description": "",
5 "main": "index.js",
6 "scripts": {
7 "test": "jest --verbose"
8 },
9 "keywords": [],
10 "author": "",
11 "license": "ISC",
12 "babel": {
13 "presets": [
14 "es2015",
15 "react"
16 ]
17 },
18 "jest": {
19 "scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
20 "unmockedModulePathPatterns": [
21 "<rootDir>/node_modules/react",
22 "<rootDir>/node_modules/react-dom",
23 "<rootDir>/node_modules/react-addons-test-utils",
24 "<rootDir>/node_modules/fbjs"
25 ]
26 },
27 "devDependencies": {
www.hackingwithreact.com @twostraws
How to Configure Jest to Test React and ES6 89
28 "babel-core": "^6.3.15",
29 "babel-jest": "^6.0.1",
30 "babel-loader": "^6.2.0",
31 "babel-preset-es2015": "^6.3.13",
32 "babel-preset-react": "^6.3.13",
33 "jest-cli": "^0.8.2",
34 "react-addons-test-utils": "^0.14.3",
35 "react-hot-loader": "^1.3.0",
36 "webpack": "^1.12.9",
37 "webpack-dev-server": "^1.14.0"
38 },
39 "dependencies": {
40 "chance": "^0.8.0",
41 "history": "^1.13.1",
42 "react": "^0.14.3",
43 "react-dom": "^0.14.3",
44 "react-router": "^1.0.2",
45 "superagent": "^1.5.0"
46 }
47 }
With that change saved, we can now run our test through Jest by running this command:
Of course it doesn’t actually do anything yet other than print its version number, because we haven’t
written any tests. Let’s do that now…
Running Jest without any tests is a good idea if only to make sure you have it set up correctly.
www.hackingwithreact.com @twostraws
Creating our First React Test with Jest 90
__tests__/List-test.js
1 jest.autoMockOff();
2
3 import React from 'react';
4 import ReactDOM from 'react-dom';
5 import TestUtils from 'react-addons-test-utils';
6
7 const List = require('../src/pages/List').default;
8
9 describe('List', () => {
10 it('renders three repo links', () => {
11 const rendered = TestUtils.renderIntoDocument(
12 <List />
13 );
14
15 const repos = TestUtils.scryRenderedDOMComponentsWithTag(rendered, 'li');
16
17 expect(repos.length).toEqual(3);
18 });
19 });
That’s almost all new code so I want to explain it all to you. But first, save that file then run the test
by using npm run test. You should see output like this:
www.hackingwithreact.com @twostraws
Creating our First React Test with Jest 91
The test was successful! That’s no great surprise, as I already said, but at least it shows you have Jest
set up correctly.
Let’s talk through all the code in that file, because you’ve seen only a few lines of it before:
That’s a raw description of what the code does, but three things deserve further clarification.
First, Jest encourages you to use natural language to express what you’re trying to test and why.
This is part of the behavior-driven development approach, and it’s important because it forces you
to focus on what you’re trying to test rather than how it works. So, we can read each test out loud:
“it renders three repo links”.
Second, the method scryRenderedDOMComponentsWithTag() has a curious name. “Scry” (which
rhymes with “cry”, if you were wondering) is an archaic word meaning “to gaze into a crystal
www.hackingwithreact.com @twostraws
Creating our First React Test with Jest 92
ball”, i.e. fortune telling. Clearly hipsters are intent on making it cool once more, so you can scry
into your rendered document to look for things.
Third, we tell Jest what we expect to happen using its expect() function. Again, this is designed to
be read aloud like plain English: “expect repos length to equal 3.” If this expectation is matched (i.e.,
if our component outputs three repos) then the test is considered a success. Any variation from the
expected result is considered a test failure.
We’re using Jest with the –verbose option, which provides more detailed feedback on each test it runs.
www.hackingwithreact.com @twostraws
Using Jest to Test Our React Components 93
We’re going to put all four of those into one test suite, in one file: inside your __tests__ folder please
create Detail-test.js. Give it this initial content:
__tests__/Detail-test.js
1 jest.autoMockOff();
2
3 import React from 'react';
4 import ReactDOM from 'react-dom';
5 import TestUtils from 'react-addons-test-utils';
6
7 const Detail = require('../src/pages/Detail').default;
8
9 describe('Detail', () => {
10 // tests go here
11 });
Before we write our four tests, we’re going to make a tiny change to the Detail component. The
GitHub API is great because it provides lots of interesting data without having to register for an
API key, but this free access is rate limited, which means you get to make only 60 requests per hour
before your access is temporarily paused.
When working on a real application, you would use “mocking” to make a simulated GitHub API
request so that a) your tests run faster, and b) you never have to worry about rate limits again. But
mocking introduce a new set of complexities that would be a bit overwhelming at this point in your
www.hackingwithreact.com @twostraws
Using Jest to Test Our React Components 94
React career, so we’re going to insert a hack: we’re going to modify the Detail component so that if
an empty repo name is passed it skips doing any Ajax calls.
This hack is helpful because it reduces the number of Ajax calls our tests will make, which in turn
reduces the likelihood of us hitting GitHub’s API limits. Without this hack, each time the Detail
component loads it makes three API calls, so across four tests that makes for a total of 12 API calls
every time we run our test suite – we’d get through our entire API limit in just five runs!
Let’s insert the hack now. Modify the fetchFeed() method in Detail.js to this:
src/pages/Detail.js
fetchFeed(type) {
if (this.props.params.repo === '') {
// empty repo name, bail out!
return;
}
Time for the first test: are there zero commits when the component first loads? This is just a matter
of rendering the component then checking that its state.commits property has a length of 0. But
this also gives me a chance to show you how to pass a fake path to the Detail component. Add this
test in place of the // tests go here comment:
www.hackingwithreact.com @twostraws
Using Jest to Test Our React Components 95
__tests__/Detail-test.js
it('starts with zero commits', () => {
const rendered = TestUtils.renderIntoDocument(
<Detail params={{repo: ''}} />
);
expect(rendered.state.commits.length).toEqual(0);
});
You’ve seen how JSX lets us use braces, { }, to mark JavaScript expressions inside markup, but now
I’m using double braces, {{ }}. This isn’t some special syntax, it’s just a combination of { meaning
“start a JavaScript expression” then { meaning “start a JavaScript object.”
You learned near the beginning of this book that setting attributes on a React component gets
translated to props. Well, in this case we’re setting a prop called params and giving that a repo
property containing an empty string. When our Detail component receives that, it will be able to
read this.props.params.repo, which is exactly what it would have received from React Router.
The second test for this component is to check that the component is set to render commits by
default. This is very similar to the test we just wrote, so I recommend you give it a try now to make
sure you understand how Jest works.
Go on, I’ll wait.
Of course, I don’t want you to peek at my solution by accident, because then you wouldn’t learn
anything.
Still here? OK, here’s a joke: what’s red and smells like blue paint?
Red paint.
I’m going to assume you’ve either written the test yourself and learned a lot, or you’re one of those
types who refuses to make a book tell them what to do and you want me to write the solution for
you. So, here goes - add this to Detail-test.js beneath the existing test:
__tests__/Detail-test.js
it('shows commits by default', () => {
const rendered = TestUtils.renderIntoDocument(
<Detail params={{repo: ''}} />
);
expect(rendered.state.mode).toEqual('commits');
});
Easy, right? Right. But that was just to make sure you were paying attention: the next test is harder,
because it needs to click the Forks button and make sure the component’s state was updated correctly.
www.hackingwithreact.com @twostraws
Using Jest to Simulate User Interaction on a React Component 96
Fortunately you heeded my warning and used the approach that passes a string parameter, right?
Right. That means we can write our third test really easily: we just find all the buttons in the
document, read the button at index 1, simulate a click on it, then make sure the Detail component’s
mode state is set to ‘forks’.
__tests__/Detail-test.js
There are three things there that might be interesting. First, we need to use index 1 for the “forks”
button because JavaScript arrays count from 0. In our detail component, button 0 is “Commits”,
button 1 is “Forks” and button 2 is “Pulls”.
Second, the TestUtils.Simulate.click() method is new, and I hope it’s pretty self-explanatory: it
simulates a click on something in our document, in this case a button.
www.hackingwithreact.com @twostraws
Using Jest to Simulate User Interaction on a React Component 97
Third, this code is a bit fragile and thus not really suitable for use in production. The reason for this
is the way the button is accessed: we assume the forks button is at index 1, but if a designer come
along and moves it then the test will break even though the button is there and still functioning.
The way to fix this is to give each button a unique ref property, which is React’s way of identifying
things that have been rendered. Please amend the end of the render() method in Detail.js to this:
src/pages/Detail.js
return (<div>
<p>You are here:
<IndexLink to="/" activeClassName="active">Home</IndexLink>
> {this.props.params.repo}</p>
{content}
</div>);
Now that each button has a unique ref property we can find it directly, without even having to call
scryRenderedDOMComponentsWithTag(). Update the third test to this:
__tests__/Detail-test.js
www.hackingwithreact.com @twostraws
Using Jest to Simulate User Interaction on a React Component 98
As you can see, we can just dip right into the refs property to find forks, and that new test will
work regardless of whether the button gets moved to in the future.
www.hackingwithreact.com @twostraws
Time for Ajax: Using Jest with Asynchronous Tests 99
expect(rendered.state.forks.length).toEqual(30);
});
That loads our <Detail> component using the repo name ‘react’ (thus forcing it to behave like
the user had browser to /detail/react), then immediately checks to see whether there are 30 forks
available. This fails because the Ajax call is Asynchronous (that’s what the first A in Ajax means)
which means it won’t stop other code from running while the data is being fetched.
There are a number of possible solutions to this problem, and we’re going to look at two of them.
The first solution is to use Jest’s waitFor() and runs() functions. The first of these, waitFor(),
checks a condition once every 10 milliseconds to see whether the condition has become true. As
soon as it becomes true, we can check our expected is present, which is where the runs() function
comes in: code you run inside a runs() function will only execute once code inside the waitsFor()
function completes.
The way this actually works is through anonymous functions, which makes it all very flexible.
That is, waitsFor() will pause until the anonymous function you create returns true, which means
you can check any number of complex conditions in there. Behind the scenes, waitFor() calls this
function once every 10 milliseconds, and will carry on checking until it returns true.
There’s a small catch, though: what if there’s a network problem and it takes a minute for GitHub
to return values? In this case, a collection of network tests might take hours to run, which is likely
to cause problems. To make things easier, waitFor() lets you specify a timeout in milliseconds: if
your anonymous function does return true within that time, it’s considered a failure and an error
message is printed out.
Enough theory: time for some code. Add this test beneath the existing three:
www.hackingwithreact.com @twostraws
Time for Ajax: Using Jest with Asynchronous Tests 100
__tests__/Detail-test.js
waitsFor(() => {
console.log('In waitFor: ' + rendered.state.forks.length);
return rendered.state.forks.length > 0;
}, "commits to be set", 2000);
runs(() => {
expect(rendered.state.forks.length).toEqual(30);
});
});
About half of that is the same as the broken example above, but note that I moved the expect() call
to be inside runs() using an anonymous function – that’s the () => { jumble of symbols.
The new part is the waitsFor() code, which again creates an anonymous function. This function
does two things, it prints a message to the console, then checks whether our Detail has loaded any
forks from GitHub. Save your file then run npm run test from the command line.
Now, the reason I had that console.log() call is so that you can see exactly how waitFor() works.
The output from the test will be something like this:
In waitFor: 0
In waitFor: 0
In waitFor: 0
In waitFor: 0
In waitFor: 0
(many lines trimmed)
In waitFor: 0
In waitFor: 0
In waitFor: 0
In waitFor: 30
PASS __tests__/Detail-test.js
Remember I said that waitsFor() calls your function once every 10 milliseconds? Well, there’s your
proof: every time you see In waitFor it’s because your function is being called to check whether it
www.hackingwithreact.com @twostraws
Time for Ajax: Using Jest with Asynchronous Tests 101
returns true. In our test we use return rendered.state.forks.length > 0; which means “return
true is fork has any items in it, otherwise return false.”
When that function finally finds 30 items in rendered.state.forks, it returns true and waitFor()
exits. Like I said, though, it’s possible network gremlins creep in, which is where the second and
third parameters for waitFor() come in: the second is "commits to be set" and the third is 2000.
This tells waitFor() to wait up to a maximum of 2000 milliseconds (2 seconds), and if that time
passes to fail with the message “timeout: timed out after 2000 msec waiting for commits to be set”
– that last part is the text we provided.
That’s all four of our tests written, but we’re not done with testing just yet. Before continuing, I
suggest you remove the console.log() statement from the fourth test otherwise it will get annoying.
If you place a console.log() call inside your waitFor() function you’ll see it being called every 10 milliseconds.
www.hackingwithreact.com @twostraws
Mocking Ajax with Jest: Making an Asynchronous Test Become Synchronous 102
__tests__/Detail-test.js
it('fetches forks from a local source', () => {
const rendered = TestUtils.renderIntoDocument(
<Detail params={{repo: ''}} />
);
expect(rendered.state.forks.length).toEqual(30);
});
As you can see, we load a JSON file called forks.json (see below) into the constant testData, then
use the setState() method of our component to force it to load. Finally, we can expect() that the
rendered.state.forks property be set to 30 – easy!
www.hackingwithreact.com @twostraws
Mocking Ajax with Jest: Making an Asynchronous Test Become Synchronous 103
One minor hiccup: we don’t actually have the file forks.json yet, but that’s easily fixed with a simple
terminal command:
Make sure you run that from the same place you were running npm run test, and it will download
the GitHub forks feed for React to the file __tests__/forks.json.
Save your changes then run the full suite of tests now – you should see passes all the way down.
www.hackingwithreact.com @twostraws
Cleaning up Our Tests: Last Tweaks 104
Sadly, that won’t work: Jest will find 31 <p> tags in the page and fail the test. This happens because
our page already has one <p> tag on there showing our breadcrumbs, so we have the 30 <p> tags
from the forks plus one more from the breadcrumbs.
There are a few solutions here. Option 1: remove the breadcrumbs. This would work, but means
giving up a nice feature of our app. Option 2: render the commits, forks and pulls using a different
tag name, such as <li>. This would also work, and doesn’t require losing a feature, so this is certainly
possible.
But there’s a third option, and it’s the one we’ll be using here: scryRenderedDOMComponentsWith-
Class(). This lets you find all tags based on their CSS class name rather than their tag name. This
class name doesn’t actually need any style information attached to it, so all it takes is to adjust the
renderCommits(), renderForks(), and renderPulls() methods of our Detail component from this:
src/pages/Detail.js
…to this:
src/pages/Detail.js
Back in the test code, we can now use scryRenderedDOMComponentsWithClass() to pull out exactly
the things we mean, regardless of whether they are <p>, <li> or anything else:
www.hackingwithreact.com @twostraws
Cleaning up Our Tests: Last Tweaks 105
__tests__/Detail-test.js
There’s just one more thing we’re going to do before we’re finished with testing, which is to take a
cold, hard look at this line:
__tests__/Detail-test.js
This is another example of code that works great but is still less than ideal. This time it’s because
we’re breaking the fourth wall of object-oriented programming: our test is forcing a new state on
our component rather than making a method call. If in the future you update the Detail component
so that setting the forks state also calls some other code, you’ll have to copy that code into your test
too, which is messy and hard to maintain.
The correct solution here is to use an OOP technique called encapsulation, which means our test
shouldn’t try to peek into and adjust the internals of our Detail component. Right now all our tests
do exactly that: they read and write the state freely, which isn’t very flexible going forward. I’m
going fix one of these with you right now, but you can fix the others yourself if you want to.
We need a new method in the Detail component that updates the component state. This can then
be called by our test to inject the saved JSON cleanly rather than by forcing a state. Realistically all
we need to move one line of code out of the fetchFeed() method and wrap it into its own method.
Find this line:
src/pages/Detail.js
That uses a computed property name along with the response body from SuperAgent in order to
update our component state. We’re going to make that a new method called saveFeed(), which will
take the type and contents of the feed as its parameters:
src/pages/Detail.js
saveFeed(type, contents) {
this.setState({ [type]: contents });
}
You can now call that straight from the fetchFeed() method:
www.hackingwithreact.com @twostraws
Cleaning up Our Tests: Last Tweaks 106
src/pages/Detail.js
If you’ve made the correct changes, the two methods should look like this:
src/pages/Detail.js
fetchFeed(type) {
if (this.props.params.repo === '') {
// empty repo name – bail out!
return;
}
saveFeed(type, contents) {
this.setState({ [type]: contents });
}
With that new saveFeed() method in place, we can update the fifth test to use it rather than forcing
a state on the component:
www.hackingwithreact.com @twostraws
Cleaning up Our Tests: Last Tweaks 107
__tests__/Detail-test.js
const forks =
TestUtils.scryRenderedDOMComponentsWithClass(rendered, 'github');
expect(forks.length).toEqual(30);
});
That shows you the technique of encapsulating your component’s state behind a method call, which
will make your code much more maintainable in the future. Yes, it’s extra work in the short term,
but it will pay off when you aren’t up at 3am trying to debug an obscure problem!
I’m not going to go through and adjust the rest of the tests, because that’s just pointless repetition –
you’re welcome to consider it homework if you want to try.
www.hackingwithreact.com @twostraws
Linting React using ESLint and Babel 108
ESLint is a fast, modern linter that scans source code written in ES6. I’ve included the babel-eslint
and eslint-plugin-react packages as well, because we’re using JSX that needs to be read in by Babel
before it hits ESLint.
Just like we did with testing, we’re going to add a dedicated command to lint our code. To do this,
open package.json in your text editor and modify the scripts section to this:
package.json
1 "scripts": {
2 "test": "jest --verbose",
3 "lint": "eslint src"
4 },
Make sure you have a comma on the end of the "test" line otherwise your package file will be
invalid.
That new “test” command tells ESLint to read our src directory, which is all we need.
www.hackingwithreact.com @twostraws
Linting React using ESLint and Babel 109
There’s just one more configuration step required, which is to create a basic ESLint configuration file.
This is rather unhelpfully named .eslintrc, and that leading period means the file will be invisible
to Mac and Linux users. Create a new file in your text editor and give it this initial content:
.eslintrc
1 {
2 "parser": "babel-eslint",
3 "env": {
4 "browser": true,
5 "node": true
6 },
7 "rules": {
8 "quotes": [2, "single"]
9 }
10 }
Now save it with the exact name .eslintrc. If you’re smart, you’ll leave the file open in your editor
because it’s hidden.
Save that file, then run npm run lint in your terminal window. It will think for a moment or two
then do nothing – but that doesn’t mean our code is perfect, just that ESLint is installed and ready
to be configured.
Files that start with a period are hidden on OS X and Linux, so it’s best to keep them open in your text editor.
www.hackingwithreact.com @twostraws
Linting React using Airbnb’s ESLint Rules 110
We now just need to tell ESLint that our own rules extend their Airbnb rules. This uses the Airbnb
rules as a foundation, adding our own overrides as needed. Modify your .eslintrc file to this:
.eslintrc
1 {
2 "parser": "babel-eslint",
3 "env": {
4 "browser": true,
5 "node": true
6 },
7 "extends": "airbnb",
8 "rules": {
9 "indent": [2, "tab"]
10 }
11 }
There’s still only one rule in there, but I’ve modified it to something deeply contentious because
we’re almost at the end now so I feel it’s safe to take some risks. This new rule means “make sure I
use tabs for indenting rather than spaces,” and if that doesn’t give you enough motivation to search
www.hackingwithreact.com @twostraws
Linting React using Airbnb’s ESLint Rules 111
for ESLint configuration options, I don’t know what will! (Note: if you either don’t want tabs or
don’t want to figure out how to set something else in the linter options, just delete the rule.)
Anyway, save your new configuration file and run npm run lint in your terminal window. This time
you’ll see lots of errors fill your screen, all telling you what the problem was as well as a filename
and line number. Note that these errors are all stylistic rather than actual bugs, but like I said it’s
important to fix these issues too if you want clear, clean, maintainable code.
Let’s tackle the easy ones first, starting with “Newline required at end of file but not found”. You
might see this one a few times, and it’s trivial to fix: just add a blank line to the end of every file
where you see this warning. Another easy one is “Missing trailing comma,” which just means that
code like this:
this.state = {
events: []
};
this.state = {
events: [],
};
The extra comma doesn’t add much, but it does reduce the chance of you adding more properties
without first adding a comma. Warning: don’t do this in JSON files such as package.json, because
many parsers will be deeply unhappy.
There are two more easy ones to fix if we choose. First, “Unexpected console statement” just means
ESLint doesn’t want us to use console.log() in our code, but this is only a warning not an error so
I’m happy to ignore this in my own code – it’s down to you if you want to remove them in yours.
Second, “‘Link’ is defined but never used” in User.js. To fix this problem, change this line:
src/pages/User.js
…to this:
www.hackingwithreact.com @twostraws
Linting React using Airbnb’s ESLint Rules 112
src/pages/User.js
Unless your code is very different from mine, that should fix all the easy linter errors. Now it’s time
for the harder stuff…
www.hackingwithreact.com @twostraws
How to Add React Component Prop Validation in Minutes 113
src/pages/App.js
App.propTypes = {
children: React.PropTypes.node,
};
Note: when I say “directly after the end” I mean after the closing brace for the class, but before the
export default App line, like this:
www.hackingwithreact.com @twostraws
How to Add React Component Prop Validation in Minutes 114
src/pages/App.js
If you want to see what happens when React detects the wrong prop type being used, try using
React.PropTypes.string in the snippet above. As you’ll see, your page still loads fine, but an error
message should appear in your browser’s debug console.
Once you’re using prop validation, React will warn you if you try to use the wrong kind of data.
We need to add two more propTypes declarations in order to make our code get cleanly through
linting. Both are the same, and say that the component can expect a params property that is an
object. Add this directly after the end of the Detail class:
www.hackingwithreact.com @twostraws
How to Add React Component Prop Validation in Minutes 115
src/pages/Detail.js
Detail.propTypes = {
params: React.PropTypes.object,
};
And add this directly after the end of the User class:
src/pages/User.js
User.propTypes = {
params: React.PropTypes.object,
};
That’s it! If you run the command npm run lint now you should see no more errors.
www.hackingwithreact.com @twostraws
Bringing it all Together: Project Complete! 116
1. How to install Webpack, Babel and React for development with ES6.
2. How to create a basic React component and import it into an application.
3. How to write simple JSX to render content.
4. How to use props to give a component values.
5. How to render several elements at once.
6. How to handle events such as onClick.
7. How to use state, and how it differs from props.
8. How to loop over and render data in an array.
9. How to fetch data from GitHub using SuperAgent and Ajax.
10. How to use string interpolation and computed property names.
11. How to pass parameters using onClick.
12. How to create routes using React Router.
13. How to create links between pages using <Link>.
14. How to render default content using <IndexRoute>.
15. How to store your React Router routes separately from your code.
16. How to make breadcrumbs using <Link> and <IndexLink>.
17. How to use Jest to test React component rendering.
18. How to use Jest to simulate user interface with controls.
19. How to create asynchronous tests using waitFor() and runs().
20. How to fake an Ajax call using the require() function.
21. How to find rendered components using scryRenderedDOMComponentsWithClass().
22. How to lint your React code using ESLint and Babel.
You might find you need to re-read some chapters to help refresh your memory, but that’s perfectly
normal – we’ve covered a huge amount of ground in just a few hours, so it takes some time to sink
in. The end result is that you’ve written a lot of code, which is good. And it’s not just any code:
www.hackingwithreact.com @twostraws
Bringing it all Together: Project Complete! 117
you’ve written tests for it and ensured it matches strict linting, so this is code you should feel proud
of.
If you’re looking to expand this project further, you have a huge amount of scope to work with
thanks to the GitHub API offering up lots of data. For more information about all the end points it
offers, see https://developer.github.com/v3/.
I would suggest, however, that from here on you consider taking a test-driven approach. I kept Jest
and ESLint until the very end only because it can be confusing to learn multiple technologies at the
same time, but if you’re smart you’ll write tests as you’ll go from here forwards. Running a linter
regularly will help keep your code consistent, and many web-focused text editors automatically lint
your code while you edit.
This is the end of Hacking with React – I hope you feel like you’ve learned a lot, I hope you’re
excited to use React in the future, and I hope you have the confidence to make great use of other
tools such as React Router and Jest. I really look forward to seeing what you make in the future –
please do send me a tweet @twostraws with a link to your React apps!
www.hackingwithreact.com @twostraws