Intro

Welcome to A Complete Intro to React. The goal of this workshop is to get you full up to speed on modern development and give you an idea what it is like to develop an app in the React ecosystem.

Make sure sure you are on the right version of this workshop, v3. If you are unsure as to which version you need, go here.

The creation of this course is sponsored by Frontend Masters. There are videos that go along with it and I encourage you to check them out.

When talking about React, you cannot simply just use React. It is an incomplete picture; it is a cog in the machine. React does well to introduce some useful primitives into your toolbox and allows you to build your app without introducing too many opinions. As such, we need to learn some other tools to round the whole story of our app. However, keep in mind that these are modules we are choosing to plug together; you are free to go home and swap in your own parts to suit your own needs. While React fits many/most needs, this complete stack won't; it has opinions and you need to make sure each piece contributes to your story and try not to shoehorn any piece in that you don't need.

In addition to React, we are going to be using Node.js, Express, Redux, Webpack, Jest, Enzyme, Yarn, Prettier, ESLint, and React-Router. Don't worry if you're aren't familiar with any of these: that's the point of this workshop. Since this workshop is about React, we will not be deep diving into Node.js or database schema. We will discuss these satellite concepts just as they pertain to React.

Questions? Feel free to tweet at me. Corrections or concerns about this page? Please file an issue or make a pull request on the repo.

·

Setup

In order to be able to complete this entire workshop, we need to go ahead and get some tools downloaded.

Clone the git repo

Click here to go clone the start

node.js 6+

You probably can complete this with something less than node 4 but I'm using v6.9.5. Being a Node.js LTS release, it's a safe bet to be using. If you need to use a different version of Node for work, I strongly recommend using nvm. I recommend getting the binary straight off the Node website and not using homebrew for installation.

Yarn

We're going to use Yarn for this workshop. In practice this will be a little differences to you, but what's happening under the hood will be good for your app. Yarn will essentially replace your usage of the npm CLI client in your app. Instead of npm install --save react you'll run yarn add react. This installs the same package from the same npm registry (it runs through their own proxy but as of writing the registry.yarnpkg.com address is just a dumb passthrough.)

If you haven't installed yarn, install it via npm install --global yarn. You can also install it via homebrew: brew update && brew install yarn.

Yarn does a couple of things different from npm. One, it's 100% deterministic. Deterministic is just a fancy way of saying that if you run yarn from any state, any time, 1000x times, it will still work the same way the 1001st time. npm's installs are nondeterministic. If you run it from various states, it will install different ways.

Yarn does some better caching too. In fact, it does it so well you'll see a significant reduction in your install times. Big code bases have seen a 10x reduction in install times.

Yarn also locks down your dependencies by default. It's possible to do this with an npm shrinkwrap command but if you've ever had to maintain one of those, it can be messy. This locking down of dependencies means you don't have to rely on npm authors doing semver correctly, a notoriously controversial subject

yarn installs

Run yarn from the directory where you downloaded the repo. If you have node and npm installed, you should see a list of dependencies being installed.

yarn global installs

Run the following global npm installs

yarn global add jest@v19.0.2
yarn global add nodemon
yarn global add webpack@v2.2.1
yarn global add prettier@v0.22.0
yarn global add eslint@v3.18.0

I have you install specific versions so that if the libraries change at all this workshop will all still apply. It doesn't really matter version of nodemon you use. You don't necessarily need these exact versions but you may run into issues if you use different major or minor releases as things make break between versions.

·

React

Welcome to the wonderful world of React. We are going to start with the most absolutely barebone version of React. No JSX. No ES6. No transpilation. Just pure component-oriented pleasure of coding.

So let's talk about what React is. I imagine most of those who take this class will come from another framework or library, be it jQuery, Angular, Backbone, Ember, Knockout, or something else. I will draw comparisons and contrasts to these other frameworks (despite not being the same, I'll be using the word library and framework interchangeably for brevity's sake) to help illuminate some of the differences but you needn't have programmed in these other frameworks to understand this workshop.

React is a library unto itself: it's not a full fledged framework like Ember or Backbone. It does not demand to own everything in the app. It can be happy being a side component on your page and being fed data into it. Some people like to bill React the V in MVC but this is selling it short; it's more than just a view. You can make full-fledged apps with just React and no other libraries. The concerns you would normally put into the model and controller can be done in React, and just as well outside of it. Its philosophy is more similar to Angular's: React gives you new primitives so you can construct your own applications / framework.

It bears mentioning that we are slowly going to build a full stack app progressively over the course of this workshop, piece-by-piece. By the end of the workshop, you'll have built a app that simulates the Netflix experience.

My First Component!

So let's start our first React code!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vidflix</title>
</head>
<body>
    <div id="app"></div>
    <script src="node_modules/react/dist/react.js"></script>
    <script src="node_modules/react-dom/dist/react-dom.js"></script>
    <script>
        var MyFirstComponent = function() {
          return React.createElement('div', null,
            React.createElement('h1', null, "This is my first component!")
          );
        };

        ReactDOM.render(
          React.createElement(MyFirstComponent),
          document.getElementById("app")
        );
    </script>
</body>
</html>

My Second Component

Good job! You should see the text "This is my first component!" on the screen. As you may see, we constructed a bit of DOM using functions. That's all components are in React: functions. This ends up being a useful and powerful abstraction which you hopefully will see as we go on. This is about as simple as it gets as far as a React component goes. Let's take it one step further by nesting some components.

Notice we sort of have to two ideas in play here. MyFirstComponent ends up being a sort of blueprint, a rubber stamp for a type of components. When we call React.createElement we are creating one instance of MyFirstComponent, like we are stamping one stamp of it. We can create many MyFirstComponents using the same blueprint.

This method of creating components is called a stateless functional component. This is as opposed to the class version of a component which has more features. You'll see this later in the course as we keep going. Note that the React.createClass method of creating components is deprecated so we are moving on to the functions and classes versions of components. If you want to see the createClass version, see v1 and v2 of this workshop.

Before we start here, let's delete that bottom script tag in index.html and add <&NegativeMediumSpace;script src="js/ClientApp.js"></script> so we can get some good code separation going. Once done with that, create the js directory and add the ClientApp.js file. In this file, let's put

/* global React ReactDOM */

var MyTitle = function () {
  return (
    React.createElement('div', null,
      React.createElement('h1', null, 'Check out this component!')
    )
  );
};

var MyFirstComponent = function () {
  return (
    React.createElement('div', null,
      React.createElement(MyTitle, null),
      React.createElement(MyTitle, null),
      React.createElement(MyTitle, null)
    )
  );
};

ReactDOM.render(
  React.createElement(MyFirstComponent),
  document.getElementById("app")
);

Cool, right!

MyTitle is a ReactComponent class. This is a stateless functional component that, once invoked, is expected to return a React element.

To invoke this function and create a new instance of the MyTitle component, you have to use the React.createElement method. The resulting instance is a ReactElement.

We can use this element the same way we use any other HTML-native tag. This allows us to encapsulate style, behavior, and markup into one neat little package and reuse these components everywhere!

To sum-up, we're making functions that create the component and then we're using createElement to create an instance of that class, resulting in an element that can be used in others components.

Factories

This is a bit verbose to write React.createElement so many damn times. Let's use a shortcut.

// replace MyFirstComponent
var ce = React.createElement;

//replace MyFirstComponent's body
return (
  ce('div', null,
    ce(MyTitleFact, null),
    ce(MyTitleFact, null),
    ce(MyTitleFact, null)
  )
);

This just helps us from having to write React.createElement so many times. There are DOM helpers and an additional package you can bring in that creates helper functions but in all honesty we're not going to use this way of creating React components once we get to using JSX.

Props

Our title component is cute but not super reuseable. Let's make it a bit more flexible by using some props. Props are variables that you pass from the parent to the child but the child cannot modify the props it gets. This simple restriction helps a lot in the future because when bugs arise, you know the child didn't modify the variable because it can't! Let's see how to do it.

Props are received as a parameter to stateless functional components and are on the this contextual object for stateful ES6 class components.

/* global React ReactDOM */

var ce = React.createElement;

var MyTitle = function (props) {
  return (
    ce('div', null,
      ce('h1', null, props.title)
    )
  );
};

var MyFirstComponent = function () {
  return (
    ce('div', null,
      ce(MyTitle, {title: 'House of Cards'}),
      ce(MyTitle, {title: 'Orange is the New Black'}),
      ce(MyTitle, {title: 'Stranger Things'})
    )
  );
};

ReactDOM.render(
  ce(MyFirstComponent),
  document.getElementById("app")
);

Now we can change the contents of the title. But since we can pass in lots of props, we can widely differing elements of the same class based on what props are passed into the element. Let's take it a step further (and show you how to do inline styles and attributes with React.)

// change MyTitle's inside h1
ce('h1', {style: {color: props.color}}, props.title)

// change MyFirstComponent inside div
ce(MyTitle, {color: 'rebeccapurple', title: 'House of Cards'}),
ce(MyTitle, {color: 'peru', title: 'Orange is the New Black'}),
ce(MyTitle, {color: 'burlywood', title: 'Stranger Things'})

Let's stop there and switch our attention a bit to tooling. So far we've been writing React with no compile step which is pretty cool and not something enough do in the course of React. Certain things will just make sense because you know what it complies to. In any case, onward!

·

Tooling – Prettier and npm

Before we can introduce JSX to React, we are going to have to send it through a compilation step. So we are going to take a brief repose from React here to start working on our tooling a bit.

Prettier

Prettier is an amazing tool from the brain of James Long. James, like many of us, was sick of having to constantly worried about the style of his code: where to stick indents, how many, when to break lines, etc etc. Coming from languages like Go, Reason, or Elm where all that is just taken care of by the tooling for the language, this quickly wears. James did something about it and made a tool to take care of it: Prettier.

Prettier is a really fancy pretty printer. It takes the code you write, breaks it down in to an abstract syntax tree (AST) which is just a representation of your code. It then takes that AST, throws away all of your code style you made and prints it back out using a predefined style. While this sounds a little scary, it's actually really cool. Since you no longer have control of the style of your code, you no longer have to think at all about it. Your code is always consistent, as is the code from the rest of your team. No more bikeshedding!! As I like to put it: if your brain is a processor, you get to free up the thread of your brain that worries about code styles and readability: it just happens for you. Don't like semicolons? Don't write them! It puts them in for you. I love Prettier.

Need to tool around a bit with it before you trust it? Go here. You can see how it works.

Let's go integrate this into our project. It's pretty easy (lol.)

Either install Prettier globally yarn global add prettier (I'm using v0.22 as of writing) or replace when I run prettier with (from the root of your project) ./node_modules/.bin/prettier. From there, run prettier script.js. This will output the formatted version of your file. If you want to actually write the file, run prettier --write script.js. Go check script.js and see it has been reformatted a bit. I will say for non-JSX React, prettier makes your code less readable. Luckily Prettier supports JSX! We'll get to that shortly.

Prettier has a few configurations but it's mostly meant to be a tool everyone uses and doesn't argue/bikeshed about the various code style rules. Here they are. I'm going to use a print-width of 120 and single quotes because I like that better but you're welcome to leave it as-is too. Prettier also can understand flow if you switch the parser but we're not going to worry about that. TypeScript support is coming.

npm/Yarn scripts

So it can be painful to try to remember the various CLI commands to run on your project, especially with Prettier because it doesn't have a config object yet (they're making one.) npm/Yarn scripts to the rescue! You can put CLI commands into it and then run the name of the tag and it'll run that script. Let's go see how that works. Put the following into your package.json.

"scripts": {
	"format": "prettier --list-different --single-quote --print-width=120 \"js/**/*.{js,jsx}\"",
},

Now you can run yarn format or npm run format and it will run that command. This means we don't have to remember that mess of a command and just have to remember format. Nice, right? We'll be leaning on this a lot during this course.

Editor Integration

This is all fun, but it's very manual. Let's go make this run automatically on save. This makes the whole process super seamless. For your editor's instructions, go here. Once you've enabled it and put it run on autosave then we're good to go! Here's the config I used for Sublime:

{
	"prettier_cli_path": "/Users/brholt/.nvm/versions/node/v6.9.5/bin/prettier",
	"node_path": "/Users/brholt/.nvm/versions/node/v6.9.5/bin/node",
	"auto_format_on_save": true
	"prettier_options": {
		"parser": "babylon",
		"singleQuote": true,
		"printWidth": 120,
	}
}

ESLint

On top of Prettier which takes of all the formatting, you may want to enforce some code styles which pertain more to usage: for example you may want to force people never use with which is valid JS but illadvised to use. ESLint comes into play here. It will lint for this problems. There are dozens of present configs for ESLint and you're welcome to use any one of them. The Airbnb config is very popular, as is the standard config (which I teach in previous versions of this class.) We're going to use the Airbnb one since it's likely the most popular one. Let's create an .eslintrc.json file to start linting our project.

Create this file called .eslintrc.json.

{
  "extends": [
    "airbnb",
    "prettier",
    "prettier/react"
  ],
  "plugins": [
    "prettier"
  ],
  "parserOptions": {
    "ecmaVersion": 2016,
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    }
  },
  "env": {
    "es6": true,
    "browser": true,
    "node": true
  }
}

This is a combination of the recommended configs of Airbnb and Prettier. This will lint for both normal JS stuff as well as JSX stuff. Run eslint script.js now and you should see we have a few errors. Run it again with the --fix flag and see it will fix some of it for us! Go fix the rest of your errors and come back. Let's go add this to our npm scripts. If you haven't checked out Airbnb's style guide it's worth a read.

lint": "eslint **/*.{js,jsx} --quiet"

Worth adding three things here:

  • With npm scripts, you can pass additional parameters to the command if you want. Just add a -- and then put whatever else you want to tack on after that. For example, if I wanted to get the debug output from ESLint, I could run yarn lint -- --debug which would translate to eslint **/*.js --debug.
  • We can use our fix trick this way: yarn lint -- --fix.
  • We're going to both JS and JSX.

You can linting and fixing to your editor too but I'll leave that to you. It can be a bit finnicky to get going. I'll be using SublimeLinter and sublimelinter-contrib-eslint (you need both.)

·

Tooling - Webpack and Babel

Now that we have clean code via a linter and can run our scripts courtesy of npm, let's work on the compilation / build of our code. In order to achieve this we are going to use two tools: Webpack and Babel.

Webpack

Webpack is an amazing tool that came out two or so years ago that captivated particularly the React crowd quick due to its ease and interesting features. We are going to only scratch the service of this powerful tool; you could give a whole workshop just on Webpack. We are going to lean on two of Webpack's core features: module compilation and the ability to plug in loaders.

As you saw with our code, it is pretty easy to put one or two components in one file. But what happens when you have an app as complex at Netflix that has hundreds, if not thousands, of components? Having just one file is untenable. You could just split them into separate files, make sure to load them in correct order, and keep a list of files in your HTML directory. But that sucks too, so we're going to introduce a build step that, while build steps suck, this will make things a bit easier.

For fun, split your MyTitle component from ClientApp.js into a new file, MyTitle.js. You will have to put the appropriate React.DOM helper methods in each file. At the bottom of MyTitle.js, add the line export default MyTitle;. At the top of ClientApp.js, put the line import MyTitle from './MyTitle'. Let's try to compile that.

You should have Webpack 2.3+ installed. Go to the directory of the project in the terminal . After that, run webpack js/ClientApp.js public/bundle.js. In your index.html, change the line of <&NegativeMediumSpace;script src="js/ClientApp.js"></script> to <&NegativeMediumSpace;script src="/public/bundle.js"></script>.

Try your browser again. bundle.js has all the stuff for both files now. Now we can keep components in their own files which is a huge win for organization. But wait, we can use Webpack for even greater code by using it to bring in node modules. In index.html, remove the other script tags so just bundle.js is left.

In ClientApp.js, remove the global comment at the top and add at the top

import React from 'react';
import { render } from ('react-dom');

// change render
render(React.createElement(MyFirstComponent), document.getElementById('app'));

In MyTitle, add:

// remove global comment, add at top
import React from 'react';

// add to bottom
export default MyTitle;

Run your Webpack command again. Try your browser again. Despite only including bundle.js, the whole app works! If you look at Webpack, you'll see it's 99% React code and some of yours. Now we can go forth making new files and including new libraries without worrying about if they are being included!

Let's add build to our npm scripts. Add to scripts in package.json "build": "Webpack js/ClientApp.js public/bundle.js",. Run yarn build from your terminal in the project root directory and see if it works. Magic!

We're also using ES6 modules here, if you're not familiar with that syntax. The import React from 'react' is semantically identical to var React = require('react'). The import { render } from 'react-dom' as far as you're concerned here is identical to var render = require('react-dom').render. For further reading, checkout 2ality's post on it.

Babel

Babel is an amazing, amazing tool that has fundamentally altered the landscape of JavaScript as we know. Though they didn't invent the idea of transpiling future syntax of JavaScript into current syntax (thanks Traceur,) they did make it so damn easy. Couple that with the fact it worked with React's JSX (which we'll talk about in a sec) and it was a perfect storm the be massively successful. Babel, in short, is amazing and should be something you consider for every project.

Babel 6 (the latest revision of Babel) took away a tiny bit of that ease of use but in return it became much easier to develop which is important when you're maintaining a massive project that's very important. In return it requires a bit of config now.

Create a new file called .babelrc. Inside .babelrc, put:

{
  "presets": [
    "react",
    ["env", {
      "targets": {
        "browsers": "last 2 versions"
      },
      "loose": true,
      "modules": false
    }]
  ]
}

Babel has the concept of plugins. Each transformation comes in the form a plugin. However ES6 and React each have a number of transformation and it's a huge pain to include each one. Thus Babel has the concept of presets, or in other words bundles of plugins. In this case, we're including all of the React and many ES6 transformations. If this were a production app we would definitely go through and only include the transformations we needed and not all of them. For example, this includes a fairly large library to get ES6 generators working. Few people these days are actually using generators and thus it's better to leave them off if you don't need them. In our case, this is a teaching app so page weight isn't a big deal to us. This will also allow us to start using JSX.

However, we've taken this a step further by using [babel-preset-env][bpe]. babel-preset-env let's you specify your target (in our case we've targeted the last two major versions of each browser) and it only includes the necessary plugins necessary. What's amazing about this is that as we go forward and browsers can support more and more, we can start shipping ES6+ code to browsers! Amazing. It updates itself.

Also, we're telling Babel to not transform modules. New to Webpack 2 is the ability to do tree shaking. Tree shaking (also known as live code inclusion, as opposed to dead code elimination) is where you start with the entry point to your program (usually an index.js) and begin working outwards, only including the parts of code your code uses. Usually this doesn't buy you much in terms of your code; we tend to not write much dead code for our own apps. However it's extremely helpful for modules you're including. If you use lodash for one function, you don't want to include the whole library. Instead, you want your bundler to just include that barebones of what you need for to work. This is what Webpack 2 and Uglify buy for us; Webpack 2 bundles and Uglify takes the dead code paths out.

Webpack loaders

So, we could use the Babel CLI to compile our code but we are already using Webpack and Webpack has a good way to send your code through Babel via its loader mechanism. Loaders are utilities that Webpack will pipe input into and accept output out of. You can use loaders to transpile things like CoffeeScript, TypeScript, or PureScript. Webpack loaders can do some other powerful things like allowing you to include CSS, inline images via encoding, and transform SVGs. We're just going to be using the JS transformation abilities for now. Run the command webpack --module-bind 'js=babel-loader' js/ClientApp.js public/bundle.js. Should take a bit longer since it's doing more. Since we're going to be using Webpack for a few other things, let's abstract that configuration from inline to a config file. Add a webpack.config.js with the following.

const path = require('path');

module.exports = {
  context: __dirname,
  entry: './js/ClientApp.js',
  devtool: 'source-map',
  output: {
    path: path.join(__dirname, 'public'),
    filename: 'bundle.js'
  },
  resolve: {
    extensions: ['.js', '.jsx', '.json']
  },
  stats: {
    colors: true,
    reasons: true,
    chunks: false
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: 'babel-loader'
      }
    ]
  }
};

What you see is essentially a config version of what we were doing with CLI flags. Now it's a bit more resilient and guaranteed consistent. If you run just the command webpack from your project directory you should get the same output. Neat. Go change you npm script to just be "webpack" now. Go try it and make sure it still works. Great! Good? Good. This should get us to a point now where we can talk about JSX.

At this point we need to make sure Prettier and ESLint ignore our compiled files: we don't care if those files are well formatted or linted. Create a new file called .eslintignore and put this in there:

node_modules/
public/

So let's also make the scripts remember build. Add this to your package.json.

"build": "webpack"

Okay! Let's keep going!

·

JSX

We have been writing our React with vanilla JavaScript up until now which frankly few people do. Most people take advantage of JSX which essentially adds HTML/XML-like syntax as a "primitive" type to JavaScript. In reality, what it does it takes the HTML you write for your components and translate them into the same calls we writing just using vanilla JavaScript.

So why JSX? People usually get pretty grossed out by the HTML in the JavaScript and say it looks like 1998 when we were still writing JavaScript in our HTML. However, I assert that markup in JS is a good thing while JS in markup is a bad thing! Here, we're keeping all the concerns of a component in one place: the markup structure, the event listeners, the state, the state mutators, everything. If the component breaks, we know it broke there. That's really valuable.

So try it. While the plain JS way isn't too bad, once you start writing JSX I almost guarantee you won't want to go back. Let's convert what we have to JSX.

Side note: good idea to install a JSX syntax highlighter. If you're using Sublime, I highly recommend the "Babel" package off of Package Control. If you're using VIM, try VimBox. I have no experience with it but I've heard it helps.

import React from 'react';

const MyTitle = props => {
  const style = { color: props.color };
  return (
    <div>
      <h1 style={style}>
        {props.title}
      </h1>
    </div>
  );
};

export default MyTitle;

We're using JSX finally! Make sure you're inserting those opening and closing parens around the component. It's just letting JS know you're going to put your expression on the next line (which we want to do for readability.)

Since we're using JSX now, we're going to rename our files to MyTitle.jsx and ClientApp.jsx. You could leave it as .js (I tend to in personal projects) but the Airbnb config of ESLint requires it. Make sure you change the entry in webpack.config.js to reflect JSX as the entry point.

Notice the curly braces surrounding props.title. They're there to let JSX know I want this to be a JS expression. If they weren't there, it would literally put the string 'props.title'. Notice the double curly braces surrounding the style value. The exterior set of curly braces are the same as the one as before: they're letting JSX know you want a JS expression. The interior set of curly braces represent a JavaScript object, meaning you're passing in a object to the style attribute. A little strange to get used to seeing that, but keep in mind that double curly braces themselves have no special meaning.

Lastly, you may notice that I switched to an ES6 style here. This is synonymous with the function syntax; just a bit more terse. Feel free to write it in any syntax that fits your fancy; this is very readable to me but may not be to you.

Let's rewrite ClientApp.jsx.

import React from 'react';
import ReactDOM from 'react-dom';
import MyTitle from './MyTitle';

const MyFirstComponent = () => {
  return (
    <div>
      <MyTitle title="Props are great!" color="rebeccapurple" />
      <MyTitle title="Use props everywhere!" color="mediumaquamarine" />
      <MyTitle title="Props are the best!" color="peru" />
    </div>
  );
};

ReactDOM.render(<MyFirstComponent />, document.getElementById('app'));

Notice how we use our own components as if they were normal HTML tags. Neat, right? We define our own components and then we can throw anywhere since they're self contained! We even pass them props as if they were normal attributes.

Also notice we're including React in both files but not directly manipulating it. This is okay since remember JSX is converting these tags to calls to React, so in reality it is using React (and thus we do have to include it.) Overall a simplification I think!

Something to make note of: the top level component has to be singular, or in other words, your top level component cannot be sibling to something else. This makes sense if you think about what JSX is transpiling to: function calls.


// won't compile
const InvalidComponent = () => (
  <h1>My Title</h1>
  <h2>My Title 2</h2>
);

// will compile
const ValidComponent = () => (
  <div>
    <h1>My Title</h1>
    <h2>My Title 2</h2>
  </div>
);

Hence why you'll see a lot of wrapping divs in JSX; it's so the whole thing will compile. This is fine if you need to do this; an extra wrapping div does nothing of harm unless you have structurally sensitive CSS. Also notice that if you have just raw text to put into JSX, you can enter it just as if was normal HTML. It's only when you have JS expressions when you need to use the curly braces.

Note this will change with React Fiber, which by the time you read this, may have been released. It's plan as shipping with React 16. Fiber is a total rewrite that's still API compatbile with React. With Fiber, you'll be able to render multiple top level components.

·

More React

So let's actually discuss what we're going to build today: a basic Netflix experience. This is going to afford us several fun experiences and use cases to explore with our new-found stack. Our app is going to have a home page, a browse/search page, and a video page. Over the next few chapters we are going to be talking about Redux and react-router and their rather-central roles to the React eco-system. But first let's keep diving into React.

Rather our toys we've made so far to demonstrate how to make React components, let's actually start building our web app. We are going to make our home page. The home page is going to have a sweet background image, our logo, a text input to search for a specific show, and another button to just browse all videos.

I have predone most of the CSS for you for this particular course for you. Later in the course we're actually going to explore using styled-components but for now let's just import the CSS I wrote for you and tackle styled-components later. Follow the structure of the HTML and class names and you'll be golden. If at any time your styles look broken as compared to mine, chances are you misnamed a class or misstructured the tags.

We are going to add a link tag to our HTML. Typically you wouldn't have both external CSS and styled-components but believe me I'm going to save you a bunch of typing by limiting how much CSS I'm going to have you write. This is not a CSS course, haha.

Add this to index.html: <link rel="stylesheet" href="/public/style.css" />. From here on out we'll have to use a webserver or else you'll have to mess with the paths to get the CSS to display correctly.

Let's start building our app. You can delete MyTitle.jsx if you desire; you can also leave it. We're not going to be using it any further. Go ahead and clear out most ClientApp.jsx and let's start putting our new app in here.

Oh, and you also have to name your video service. I named mine svideo but name your app whatever you want!

Right now it's going to be pretty simple. Drop in this:

import React from 'react';
import { render } from 'react-dom';

const App = () => (
  <div className="app">
    <div className="landing">
      <h1>svideo</h1>
      <input type="text" placeholder="Search" />
      <a>or Browse All</a>
    </div>
  </div>
);

render(<App />, document.getElementById('app'));

Just in case you're not familiar, notice that we have no curly braces and only parens around our JSX. This means there's an implicit return and it's required by the Airbnb rules. If this is confusing for you, I recommend the 2ality post on it.

Save and run npm run build. If you followed the CSS naming and HTML structure, you should see a nice looking landing page. Also a good time to make sure if your code is still lint-compliant.

So, another tooling detour here: I'm getting pretty sick of having to hit the terminal every single time to see run build. Furthermore, build for webpack is pretty slow despite how small our code is. So let's take advantage of webpack's watch feature. This will watch for every time you save rebuild automatically. Additionally it will keep the unchanged bits (like the React library) in memory so it doesn't have to rebuild it every time. Try running webpack --watch in your terminal. It will use the same config we already made. See how much faster it is after running? Let's add a new npm script.

// in package.json in scripts
"watch": "webpack --watch",

Great, right? So, another part that's been bothering me is that it's such a pain to have to re-run ESLint every time. Either that or you'll get a bunch of errors all at once when you run it before you commit. Luckily we can have Webpack run ESLint each time it compiles. It will then notify you when you have errors.

Just like we're using the babel-loader to transpile our code, we're going to use the eslint-loader to run our linting for us. eslint-loader is similar to babel-loader, except it won't change our code: just warn us of linting errors.

Let's change our webpack config to use our new eslint-loader. eslint will automatically use our .eslintrc.json, regardless is called via the CLI or programmatically via webpack.

// inside rules, before babel-loader
{
  enforce: "pre",
  test: /\.js$/,
  loader: "eslint-loader",
  exclude: /node_modules/
}

If you were already running yarn watch, stop and start it again.

Nice! Any time you save now using yarn watch it will both compile your code and lint it. Pretty slick. And it all runs so much faster. We're going to get to how to make your code reloads even faster. We do the enforce: pre part to make sure that the lint part is loaded before the build step, ensuring our uncompiled code (and not the intermediary) is always the one being linted.

Now let's switch to the Webpack dev server which makes things just that much easier. The Webpack dev server speeds up development by letting you run a local server and serve all your content from the dev server. It watches and keeps everything in memory so rebuilds go faster. Add the following to your server to get it to serve your statics correctly:

// add as a top level config item
devServer: {
  publicPath: '/public/'
},

Now try running this from the directory of your project: ./node_modules/webpack-dev-server/bin/webpack-dev-server.js. You could install this globally but we'll use npm's cli to make this easy. You should see in your CLI a bunch output from the build and probably some linter errors. Feel free to fix your errors but now you should be able to go http://localhost:8080 to see your project running. You should also see the build output in the console of the browser. Cool! Add the following line to your npm scripts: "dev": "webpack-dev-server",. Now you can do yarn dev and your code will start building and serving right away!

But first let's keep going with our app. Our landing is pretty much done for now. We want to start working on the browse all page, but we need to move onto the router to do that real quick.

·

React Router

So now we have a basic landing page and we want to be able to transition to another page. Because we making this as a single page app, we are going to use the marvelous react-router. react-router is a robust piece of technology and we are just going to be scratching the surface of it now. If you intend on making a single page app, I suggest you deep dive into it to uncover all of its potential. It also now works for React Native.

We're going to be using React Router version 4 here. This is significant because React Router has gone through several thrashes of API, from 0 to 1, from 1 to 2/3, and now from 2/3 to 4. The authors have assured us that this is the last major thrash but it quite a departure, but thankfully the thrash is worth it. It got a lot easier to use.

We are just use the top level router at the moment. First, let's move our landing page into its own component so we can use ClientApp as the entry point for the app. Move all the code (except the ReactDOM last line; leave that there) to Landing.jsx.

// Landing.jsx
import React from 'react';

const Landing = () => (
  <div className="landing">
    <h1>svideo</h1>
    <input type="text" placeholder="Search" />
    <a>or Browse All</a>
  </div>
);

export default Landing;
// ClientApp.jsx
import React from 'react';
import { render } from 'react-dom';
import { HashRouter, Route } from 'react-router-dom';
import Landing from './Landing';

const App = () => (
  <HashRouter>
    <div className="app">
      <Route exact path="/" component={Landing} />
    </div>
  </HashRouter>
);

render(<App />, document.getElementById('app'));

Cool. Make sure ESLint isn't yelling at you and that your app still works. It should appear pretty much the same to you. The HashRouter is a sort of hacky router which puts your route information into the hash of the URL (after the #). We'll use BrowserRouter later so we have nice URLs, but for now this gets us started. The Route component is a matched route; when it matchs the path provided (in this case we don't want fuzzy matching, hence the exactly attribute) it render the component provided, in this case the Landing component. It's wrapped in a div because routers can only have one child since they render them directly, but it's nice because all routes will now get the styling from the .app class for free.

Now we have a router so we're free to introduce a second page! Let's make our search page. Create a new file called Search.js. In Search.js put:

import React from 'react';

const Search = () => <h1>Search!!</h1>;

export default Search;

Put in ClientApp

import React from 'react';
import { render } from 'react-dom';
import { HashRouter, Route } from 'react-router-dom';
import Landing from './Landing';
import Search from './Search';

const App = () => (
  <HashRouter>
    <div className="app">
      <Route exact path="/" component={Landing} />
      <Route path="/search" component={Search} />
    </div>
  </HashRouter>
);

render(<App />, document.getElementById('app'));

In Landing, change the <a>or Browse All</a> to

// add at top with other requires
import { Link } from 'react-router-dom';

// change <a> to
<Link to='/search'>or Browse All</Link>

Now you have another working route (which all it's doing is showing an h1) and a link to get there. When linking between routes with react-router, use the Link component. This allows you to refactor how routes work without having refactor all of your individual links (you could just make your a's href "#/search" and it would work for now but could break later.) Now your button should work to take you to the browser page and you should be able to use back and forward for free thanks to react-router.

The HashRouter sort of sucks though so let's migrate to BrowserRouter. First, we have to make webpack-dev-server aware that it should pass unfound routes back to index.html anyway. Add the following line your webpack.config.js inside of the devServer object: historyApiFallback: true. Once you do this, restart your Webpack dev server.

Then go to ClientApp.jsx and change all references (the import and the use of the component) of HashRouter to BrowserRouter. Now instead of #/search, it should go to just /search. Yay!

·

React: Props

Let's start making our search page. We're going to start with some dummy data and work our way from there. Again, follow the same HTML structure and CSS naming as me and you'll get all the styling for free. Feel free to take a glance at ./data.json to see what's there. As you may have guessed, it's a bunch of Netflix shows. This whole workshop is actually just an elaborate advertisement for Netflix (just kidding; I promise.)

Webpack allows you to require in json files just like you would another JavaScript file so we'll take advantage of that when we start coding our new search page.

Previously Webpack required a JSON loader to load JSON files but now Webpack 2 allows it by default.

// in replace Search.js
import React from 'react';
import preload from '../data.json';

const Search = () => (
  <div className="search">
    <pre>{JSON.stringify(preload, null, 4)}</pre>
  </div>
);

export default Search;

You should see it say dump a lot of JSON to the page at the top of the page. When you use curly braces in JSX, you're telling JSX you want it run a JavaScript expression and then display whatever it returns. If you take away those curly braces (try it) you'll see it literally displays "JSON.stringify(preload, null, 4)" as a string. So that's a neat tool to have; let's take it a step further and display all of the titles as their own components.

As you may remember, JSX is transpiling your JSX-HTML to function calls. As such, you may be able to imagine that a bunch of sibling components are just an array of components. Since they're just normal ol' JavaScript arrays, we can use some functional-programming-fu to transform data into components.

// replace render's return
<div className='search'>
  {preload.shows.map((show) => {
    return (
      <h3>{show.title}</h3>
    )
  })}
</div>

You should now see all of the titles in a nice scrollable list. This is the ng-repeat/#each of React: plain ol' JavaScript map. If you are not familiar with map, read this. This is one of the reasons I love React: for the most part best practices of React are just JavaScript best practices. There's very little DSL to learn. Cool! Let's flesh out how our search results are going to look.

import React from 'react';
import preload from '../data.json';

const Search = () => (
  <div className="search">
    <div>
      {preload.shows.map(show => (
        <div className="show-card">
          <img alt={`${show.title} Show Poster`} src={`/public/img/posters/${show.poster}`} />
          <div>
            <h3>{show.title}</h3>
            <h4>({show.year})</h4>
            <p>{show.description}</p>
          </div>
        </div>
      ))}
    </div>
  </div>
);

export default Search;

Try saving and re-rendering. You should see some nice cards for the shows. Notice that we can use those fancy curly braces to insert JavaScript expressions into HTML attribute too. Neat.

However we can reorganize this a bit better: the ShowCard component can be broken out into its own component. Let's do that. Make a file called ShowCard.jsx and put this in there:

import React from 'react';

const ShowCard = props => (
  <div className="show-card">
    <img alt={`${props.show.title} Show Poster`} src={`/public/img/posters/${props.show.poster}`} />
    <div>
      <h3>{props.show.title}</h3>
      <h4>({props.show.year})</h4>
      <p>{props.show.description}</p>
    </div>
  </div>
);

export default ShowCard;

Notice we're using the props object like we did for title and color before in MyTitle. This is what we are going to be receiving from our parents. In this case, an individual ShowCard needs to receive all the necessary data from its parent to be able to display it.

This is a good time to discuss the philosophy that's sort of tough to get used to with React coding. We typically think of user interfaces as entities that change over a span of actions and events. Think of a jQuery UI you have made. Imagine making a drop down. You would have to write the code for a user clicking it to opening the drop down to the user clicking an item in the drop down. It's a progression of time, events, and interactions. Imagine if there was a bug with that final interaction. You now have to work out in your head the sequence of events to get it to that same state that the bug occurs in to able to fix it. This is second nature to many of us since we have done it so many times.

React takes a fundamentally different approach but it takes some retraining of your brain. However I argue this approach is superior due to it being much easier to reason about, making it more readable for newcomers to the code and much more debuggable. In React, you think of your UI as snapshots. You do not think of it as a progression of time and events; rather, you look at the UI as how is it going to look given a set of parameters. That's all it is. Given a set of parameters, how does this UI look? Using the drop down example, you think of the drop down in its various states: an open state, a closed state, and an event that triggers when you click the item. You represent these varying states using props and state (we'll get to state in a bit.) Given a certain set of props, the UI always looks this way. This will become more concrete as we go on.

This brings me to my next point: when coding React, assume you have all the data you need coming in via props and then figure out later how to get it there. That will make it much easier. Just assume it all works and then later go make it work.

And these principles? Not invented by React. These are battle-tested ideas that stem a lot from functional programming. There's a lot of good computer science going on here, whether or not we use React to apply these concepts.

Okay, great, let's go to Search and drop in our new component.

import React from 'react'
import ShowCard from './ShowCard'
import preload from '../public/data.json'

const Search = React.createClass({
  render () {
    return (
      <div className='search'>
        <div>
          {preload.shows.map((show) => {
            return (
              <ShowCard show={show} />
            )
          })}
        </div>
      </div>
    )
  }
})

export default Search

Much like you give an HTML tag an attribute is how you give props to children components in React. Here we're passing down an object to our child component to make it available to the ShowCard via props. Neat, right? Save it and reload the page. standard is going to give you a bunch of complaints but we're going to address that momentarily. You should see the same UI.

One of the errors you'll notice in the browser console is something like: "Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of Search." You see this because we have multiple, similiar sibling components next to each other and React doesn't have a quick way to tell them apart. If you start reordering them (like if we added a sort feature) then React would just destroy and re-create them each time since it doesn't know you're just reordering them. This is unnecessarily expensive, as you may imagine, and can really kill your performance if you have a lot of complex nodes being created and destroyed. You can give React a shortcut to be able to tell them quickly apart: give each a component a unique identifier as a key attribute. So go add it to the ShowCard component like so: <ShowCard show={show} key={show.imdbID} />.

So let's fix our lint errors now. Airbnb lint rules dictates that all props have a propType. React has a features that allows you to set propTypes which it then validates at runtime. This ends up being great for debugging because React now knows what type of props it should be getting so it can give you a meaningful error messages if there's a type mismatch or omission. So let's go fix the errors.

In ShowCard, go add this just below the declaration of the ShowCard function:

// add below import React
import { shape, string } from 'prop-types';

// add below ShowCard function
ShowCard.propTypes = {
  show: shape({
    poster: string.isRequired,
    title: string.isRequired,
    year: string.isRequired,
    description: string.isRequired
  }).isRequired
};

Now React knows to expect that show is both an object full of strings and those strings are required for the ShowCard to work. If a prop is optional (which is fine if it is indeed optional) then leave off the isRequired part. You must provide a default or the Airbnb rules are going to yell at you.

We can make this a little neater via the ES6/JSX spread operator. Let's try that. Change Search's ShowCard from <ShowCard show={show} /> to <ShowCard {...show} key={show.imdbID} />. This will take all the properties from show and spread them out as individual properties on ShowCard. You could write <ShowCard title={show.title} poster={show.poster} description={show.description} year={show.year} /> but that's a lot of writing and this cuts an easy corner. Let's go modify ShowCard to match. This is a dangerous tool: only do it if you know every property in the object is needed in the component (or if you're doing a higher order component, but we'll get to the later.)

import React from 'react';
import { string } from 'prop-types';

const ShowCard = props => (
  <div className="show-card">
    <img alt={`${props.title} Show Poster`} src={`/public/img/posters/${props.poster}`} />
    <div>
      <h3>{props.title}</h3>
      <h4>({props.year})</h4>
      <p>{props.description}</p>
    </div>
  </div>
);

ShowCard.propTypes = {
  poster: string.isRequired,
  title: string.isRequired,
  year: string.isRequired,
  description: string.isRequired
};

export default ShowCard;

We've now made our code a bit cleaner since we don't have to props.show... ad nauseam. From here we're going to move beyond prop types: we're going to incorporate Flow types. Prop types were deprecated as being part of the main package as of 15.5 since it's extra weight if you don't need them. If you're using types (like Flow or TypeScript) then they're redundant. Let's keep going.

·

styled-components 💅

You can do normal CSS (as we have doing so far) but there's been a recent interest in CSS-in-JS. There are several great approaches to this but we're going to use styled-components. The advantage of this is similar to what we see of doing JSX/React over separated templates and controllers: we get to mush together concerns for single components. If we do use CSS-in-JS, it means everything for a single component lives in a single place: we don't have to worry about including the right style sheets for the right components. It means if you include a component on a page, you get the markup, behavior, and style, guaranteed.

Let's talk about why this may be less than ideal. We have decades of browsers optimizing how to download, parse, and apply external style sheets. This way has been well thought out. I'm not saying we can't figure out how to optimize this path too; we just haven't yet. It's slower. This might be important to the performance of your app; it might not. You're also doubling your parse cost: we have to parse it as JS first, and then as CSS the second time. I think there's more around the tooling here: we can have our cake and eat it too. This is a relatively nascent idea: we'll figure it out if it pans out to be a good idea.

So the approach that styled-components takes is using ES6 tagged template literals. If this is your first encounter with them, you're not alone! It's outside the scope of this class to explore what they can do in-depth but as is customary, here's the 2ality article on it. Suffice to say that tagged template literals is a peculiar way of invoking normal functions. In our case, we give a CSS string to a styled-component function, it does some processing based on that, and spits out a valid React component. We instead of using a {div,h1,img,input,span,etc.} use the React component. Whatever normal attributes you would have given to that HTML element, you give to the styled component and it works great! Let's style our ShowCard.

import React from 'react';
import { string } from 'prop-types';
import styled from 'styled-components';

const Wrapper = styled.div`
  width: 32%;
  border: 2px solid #333;
  border-radius: 4px;
  margin-bottom: 25px;
  padding-right: 10px;
  overflow: hidden;
`;

const Image = styled.img`
  width: 46%;
  float: left;
  margin-right: 10px;
`;

const ShowCard = props => (
  <Wrapper>
    <Image alt={`${props.title} Show Poster`} src={`/public/img/posters/${props.poster}`} />
    <div>
      <h3>{props.title}</h3>
      <h4>({props.year})</h4>
      <p>{props.description}</p>
    </div>
  </Wrapper>
);

ShowCard.propTypes = {
  poster: string.isRequired,
  title: string.isRequired,
  year: string.isRequired,
  description: string.isRequired
};

export default ShowCard;

Notice we created two different elements here: a div which we turned into Wrapper, and a img which we turned into Image. We associated some CSS with these two HTML elements and used them in our JSX. That's what styled-components are! You replace HTML elements with styled ones! Pretty cool, right? We won't do this for every component in our app since I want to focus more on the JS than I do on the CSS but I wanted to show you how to do it. It's fun to play with. If you were going to do some sort of CSS-in-JS solution, you'd probably want to be all-in on it.

We'll do some on the Details page but for now, let's go back to React-y stuff.

·

React: State

We've discussed props which all you to have immutable state passed from parents to children. However, as any seasoned UI developer will point out, user interfaces are inherently stateful. You app at some level must contain some level of mutability. React gives you a very controlled window to introduce this mutability to be able to reason easily about this mutability aptly called state.

While props are passed down from parents and are immutable, state is created, read, and mutated all inside of a component. In other words, if a component has state, that state cannot be mutated by a parent, child, or any other external influence; only that same component has access to the setState method which is the only way to mutate state. That component has the ability to expose methods to children that the child can call to let the parent know it should mutate its state, but again, it is totally up to the parent to respect that call and mutate the state; the child can only call methods exposed to it via passed-down props.

So let's see this in action. We're going to add a header that allows us to search our shows.

In Search.jsx, add the following:

// inside div.search, above and sibling to the div that contains the shows
<header>
  <h1>svideo</h1>
  <input type='text' placeholder='Search' />
</header>

Now the UI is in place. You should see a little header bar at the top. Let's start tracking what's actually in the input.

So far we've been using stateless functional components. These are amazing sicne they're the least complicated and therefore have the smallest surface area for bugs for you. But now we need to graduate to a full React class. There are two ways to accomplish this: ES6 classes and React.createClass. The React team has announced their intent to deprecate React.createClass and therefore we're teaching ES6 classes today. You can still use createClass: you just have include another package. But I suggest you use ES6 since that's the future.

ES6 classes have a lot to them and we're not going to really cover them super in-depth. If you need a more complete briefing on ES6 classes, check 2ality.

So our goal here is to track the contents of the input with React. We want React to be the source of truth of what's the search query is, not what lives in the DOM. This seems like splitting hairs but it's important. With data you always want one go-to place to get the truth: any time you try to synchronize two sources of truth, it's inevitably a never-ending font of bugs. Suffice to say: we want it to live in React and then have the DOM reflect that truth.

Before we do that, let's refactor to use a class instead of a stateless functional component.

import React, { Component } from 'react';
import preload from '../data.json';
import ShowCard from './ShowCard';

class Search extends Component {
  render() {
    return (
      <div className="search">
        <header>
          <h1>svideo</h1>
          <input type="text" placeholder="Search" />
        </header>
        <div>
          {preload.shows.map(show => <ShowCard key={show.imdbID} {...show} />)}
        </div>
      </div>
    );
  }
}

export default Search;

Your ESLint will yell since this should be a stateless functional component: ignore it for now. But this is equivalent. Now that we've done that, let's add our statefulness.

  // add to top of Search class
  constructor(props) {
    super(props);

    this.state = {
      searchTerm: 'this is a debug statement'
    };
  }

// replace header
<header>
  <h1>{this.state.searchTerm}</h1>
  <input value={this.state.searchTerm} type="text" placeholder="Search" />
</header>

You'll see some errors/warnings in the console. Ignore for now.

I replaced the brand momentarily so you can see the see the searchTerm change. You should see whatever you made the initial state for searchTerm show up as the brand. Neat, right?

Now your input should have the initial state of your searchTerm. Now try and type and/or delete anything. You can't! You broke the Internet! Just kidding. But to understand why this weird bug is happening you have to understand how React handles keypresses. Your state object on your component states that the value of searchTerm is 'this is the default searchTerm'. When a keypress happens, React kicks off a re-render. Since nothing modified the value of searchTerm, it's still the same string and thus it re-renders the same string there. Your state object is the source of truth. So let's make the value of searchTerm bound to the value of the input.

In other words, two data binding is not free in React. This is an advantage because it makes React less magical and thus easier to understand. A lesser imporant facet is that it's also more performant.

// add to constructor
this.handleSearchTermChange = this.handleSearchTermChange.bind(this);

// add method
handleSearchTermChange (event) {
  this.setState({ searchTerm: event.target.value })
}

// replace input
<input
  onChange={this.handleSearchTermChange}
  value={this.state.searchTerm}
  type="text"
  placeholder="Search"
/>

Now try typing in the input. As you see, the title is now reflective of whatever you type in the search input. So let's chat about what we did here. We made an event listener that handles the change events that the input throws off when it has a keypress. That event listener accepts an event that's technically a React synthetic event but its API looks just like a normal DOM event. In the event listener, we call this.setState, a method that allows you to mutate the state and then lets React re-render. If you don't call setState and instead mutate this.state yourself, React isn't privy to the fact the fact that you're changing stuff and thus doesn't know to re-render. In other words, never modify this.state directly and always just use setState. setState works like Object.assign in that it will do a merge of your objects (it's a shallow merge and not a deep merge) so you're free to just modify the keys you need to.

One bit of weirdness is the binding of context in the constructor. Yeah, I don't like it either. Let's talk about why you need it. Inside of handleSearchTerm you call this.setState. What this is in that context is important: it must refere to the instance of the Search class. So what context are in event listeners called in? The answer is not that one you need! (It's undefined in this case.) So you need to ensure that this refers to that Search component. There are several ways to accomplish this:

  • On the input component, we could put onChange={() =>this.handleSearchTermChange()}. Since it's an arrow function, a new context won't be created and thus Search would be the context. This is bad because every time that render is called (which can vary from a lot to a whole 💩 ton) a new function is created. That sucks for performance. Bind is not a cheap function call (depending on which JS engine you're in.) So don't do it. I show you because I see it in projects sometimes so I wanted to show you what's going on.
  • We can do just what I showed you: bind it in the constructor. This is what I typically do.
  • You can use create-react-class (the package I referred to earlier.) This autobinds the context for you.
  • Use the transform-class-properties Babel transform. If I were doing a personal project I'd do this but probably not at my company. This leverages a stage 2 proposal of a feature to be added to JS: Public Class Fields. Instead of handleSearchTermChange(event) { /* body */ } you'd have handleSearchTerm = () => { /* body * }. I believe this feature will make the language, but until things reach stage 3 I'm uncomfortable adding it to a long-term-supported repo. Again, because it's an arrow function, the context will be correct.

Let's go actually implement this using class properties. Since it's stage 2, it's may change but I extracted a promise from THE James Kyle that he'd write a codemod (a JS tool that will autorefactor code for you) for any changes that occur. Keep an eye on that. Add plugins as a top level property to your .babelrc.

"plugins": [
  "babel-plugin-transform-class-properties"
],

Now go back to Search and let's use it.

// Search.jsx
// delete bind line in constructor
handleSearchTermChange = (event) => {
  this.setState({ searchTerm: event.target.value });
}

We can also simplify our declaration of initial state too instead of having the clunky constructor. Delete the constructor method and put into the Search class:

// delete constructor

// add in Search class
state = {
  searchTerm: ''
}

Now go back and make sure everything works. Voila!

So go back now and change the brand to the correct title.

Let's make the search actually do something now. Since now we have our state being tracked, let's use it do a real time search on our titles.

// replace div inside search which contains shows
<div>
  {preload.shows
    .filter(
      show =>
        `${show.title} ${show.description}`.toUpperCase().indexOf(this.state.searchTerm.toUpperCase()) >= 0
    )
    .map((show, index) => <ShowCard {...show} key={show.imdbID} id={index} />)}
</div>

This is a little clever but let's dissect the new filter line we added. We're looking at both the title and description lines to search on and using the indexOf method from strings to see if the searchTerm exists within the description or title. We use toUpperCase on both to make it case agnostic. And the filter method on arrays just filters out items in an array that the method returns false on. Now try typing in your searchBox. You should see it filter as you type. We could make this more efficient but I'll leave that to you in your own time.

If you're unfamiliar with filter, check this out. If you're unfamiliar with arrow functions, check this out. If you're unfamiliar with indexOf, look here. And finally, for template strings (the back ticks instead of the quotes for the strings) look here.

·

Testing React with Jest, Snapshots, and Enzyme

Note: This is using Jest. If you want to see how to test React components using Mocha, Chai, Sinon, and Enzyme, see the v1 version of this workshop.

Now that we have something worth testing, let's test it. We're going to be using Facebook's Jest (v19.0.2) to do it since it has some neat features. Go ahead and add the following to your npm scripts: "test": "jest". Then go to your JS directory, create a folder called __tests__ inside of js, and create a file called Search.spec.jsx. I like to make my tests live right along side the files they test but I'm okay putting them in another directory: up to you. In either case Jest is smart enough to autodiscover them if you make the extension *.spec.jsx (or *.test.jsx if you prefer that. Also .js is fine too, just not with Airbnb.) The __tests__ convention is one Facebook uses and works well. Feel free to use your own sensibilities as nothing is really set in stone here.

In Search.spec.js, put:

import React from 'react';
import renderer from 'react-test-renderer';
import Search from '../Search';

test('Search renders correctly', () => {
  const component = renderer.create(<Search />);
  let tree = component.toJSON();
  expect(tree).toMatchSnapshot();
});

Your linter is probably yelling at you by now. Add "jest": true, to the env config object. This will ESLint to not warn on Jest's top global variables.

This is a snapshot test and it's a super cool new feature of Jest. Jest is going to render out the component you tell it to and dump the state of that to a file. It's basically a free unit test that the computer generates for you. If the markup changes in the future unexpectedly, your unit test will fail and you'll see why it failed.

So then you may ask, "What happens if I update the component on purpose?" Easy! You run the test again with the -u flag and it will write out new snapshots. Awesome! Also note you're supposed to commit snapshots to git.

Okay, so go the CLI and run yarn test You're going to get some error about import being a bad token; this is true since as of Node.js 7, V8 (the JS engine that power Node.js) still doesn't understand ES6 modules, but we still want to use them in dev so we need to do some special Babel transformations just for testing. Go to your .babelrc file and put this:

{
  "presets": [
    "react",
    ["env", {
      "targets": {
        "browsers": "last 2 versions"
      },
      "loose": true,
      "modules": false
    }]
  ],
  "env": {
    "test": {
      "plugins": ["transform-es2015-modules-commonjs"]
    }
  }
}
}

This will add the correct Babel transformation when you are the test environment. Now let's make it so the jest command is run in the test environment (since by default it won't). Go back and change your line in your npm scripts to be "test": "NODE_ENV=test jest". Now it will apply that extra transformation for you. Now try running npm t again and see what happens. If it still fails on the import token, run yarn test -- --no-cache. The double dash means you want to pass parameters to whatever npm is running, in this case jest, so the command you're actually running is jest --no-cache. That's a useful trick to know. Then Jest likes to cache Babel transformations for ones it's already done so that you don't have to do it every time; this greatly speeds up running tests but it also doesn't check to see if you updated your .babelrc. So here we need to tell it to do so.

So now that you have a passing test, try modifiying Search and running it again. It'll give you a git diff type output and you can see what changed. If it's what you expect, you just re-run the command with -u at the end, yarn test -- -u. Let's actually put that as an npm script so we don't have to remember that. Add "test:update": "NODE_ENV=test jest -u" to your npm scripts in package.json.

Okay, so now we have a few problems with this test. First, if we modify ShowCard, it's going to fail this test, and I think that's a problem. As much as possible, we want a Search test to only fail if something in Search breaks, and we want ShowCard to fail if ShowCard breaks. Luckily we can do that with a tool called Enzyme from Airbnb. I show you both so you can see the easiest, more official way of doing snapshot testing (with react-test-renderer) and perhaps the more common way with Enzyme. Also, react-test-renderer and Enzyme can't be imported into the same file and we need to use Enzyme for other tests later.

So modifiy your test to read:

import React from 'react';
import { shallow } from 'enzyme';
import Search from '../Search';

test('Search renders correctly', () => {
  const component = shallow(<Search />);
  expect(component).toMatchSnapshot();
});

This still won't quite work the way we want: Jest doesn't know how to correctly serialize Enzyme components for snapshots. However the Jest team knew this would happen and gave us the ability to give a custom serializer. Add this as a top level property to your package.json:

"jest": {
  "snapshotSerializers": ["jest-serializer-enzyme"]
},

Run yarn test and you can see the difference. Instead of rendering out all the individual shows, we're rendering stubs of ShowCard with the props going into each of them. This ends up being preferable since if ShowCard breaks, it won't break this test. Run yarn test:update. You should see it updated your snapshot and now you're good to keep going. Let's test that if we search on the Search component, it displays the correct amount of shows.

// add two imports
import ShowCard from '../ShowCard';
import preload from '../../data.json';

// add new test at the bottom
test('Search should render correct amount of shows', () => {
  const component = shallow(<Search />);
  expect(preload.shows.length).toEqual(component.find(ShowCard).length);
});

Enzyme gives us lots of useful features. In this case, we can use it to do jQuery-like selections from our app. We can actually ask it, "How many times does it use this React component". Logically, based on how our app works, it should have a ShowCard for each item in the preload data, so that's what we've checked here. Let's take it a step further and see if it searches correctly.

// underneath the last test
test('Search should render correct amount of shows based on search', () => {
  const searchWord = 'house';
  const component = shallow(<Search />);
  component.find('input').simulate('change',{target:{value: searchWord}});
  const showCount = preload.shows.filter((show) => `${show.title.toUpperCase()} ${show.description.toUpperCase()}`.includes(searchWord.toUpperCase())).length;
  expect(showCount).toEqual(component.find(ShowCard).length);
});

Here we're making sure that the UI displays the correct amount of ShowCards for how many shows it should match. If I were to take this a bit further, I would extract that filter function to a module, test that, and then import that same function into the production environment and the test environment. Here we're duplicating logic which isn't the best idea.

We're using Enzyme's simulate function which simulate's an event on the UI. Do note that as of present, this does not simulate event bubbling: you need to trigger the event on the same element that has the listener. We're making sure that if we type into the search that it filters properly. Keep in mind the way it works is if you call .simulate('click', event) it's actually directly calling the onClick handler directly and not going through an event system.

Enzyme has two other "depths" of rendering besides shallow: full DOM rendering and static page rendering. Full DOM uses jsdom to put the React app into a DOM-like environment if you need to interact with the DOM APIs. Unless you really need this, avoid it. It's much slower than shallow rendering because jsdom takes a long time to bootstrap and run. You can also do static rendering which uses Cheerio to parse and interact with the resulting to HTML with a jQuery like environment. Again, I'd avoid this as it is much slower but if you need to do static analysis on the HTML, static rendering is the best way.

And now you get to hear my, Brian Holt's, opinion on unit testing in React: I don't much. This is an unpopular opinion so please evaluate your own decision here. Because my markup changes so frequently as I seek to make the best user experience I can, tests are outdated as soon as they're finished. Thus testing markup is counterproductive because they're constantly failing and out-of-date. Rather, what I do is I extract important pieces of generally-useful pieces of logic and unit test the hell out of those. That way as my markup thrashes and changes, I can still re-use battle-tested pieces of logic to power the UI.

That being said, snapshot testing is so easy and so fast to update, it seems to be worth it to me. I see open source projects using it to great effectiveness and it would be something I would integrate now if I was going to start a new project.

Snapshots can be used for more than just React components. I've seen them used for server responses and even Prettier uses them to test output for their tests. Really anything that has deterministic output and can be serialized can be the target of a snapshot test.

At this point you can go create tests for the other components but you've been taught how and you can go do so yourself. We'll move on.

·

istanbul

A good-but-not-great metric of how well you're testing your is code coverage. Basically it's how much of your code gets covered by your tests. There exist many tools to do this but we're going to use the most common in the JavaScript world: istanbul.

What istanbul is going to do? Istanbul instruments your code to see what code gets run and then lets you when your tests are covering and/or not covering other parts. Run npm test -- --coverage.

So let's implement that as an npm script. Add:

"test:coverage": "NODE_ENV=test jest --coverage"

Now if you run npm run cover you should see 100% coverage on Search and and 80% ShowCard. That means all the exported code is getting covered in a test for Search and only 4/5 of it is for ShowCard. Yay! But where are the rest of our files? Well, we're not testing them at all yet so istanbul doesn't know about them. Once you start testing those files they'll show up.

Now go to the directory of your project and open in your browser coverage/lcov/index.html. This will let explore your project and see what's being covered in our project. If you look at ShowCard, you'll see that we're not covering the render method which makes sense since shallow rendering stubs ShowCard and doesn't let it render. What this means to you, at the very least, is that outside of the render method there's no runtime error but inside of it there may be. We should add a test to test render but that's for another time!

·

A Note on Hot Module Reload

So webpack has a nifty ability to do what's called hot module reload (HMR.) If you've ever used LiveReload's CSS injection, this will sound familiar. HMR will take your code, compile it on the fly, and then inject it into your live-running code. Pretty cool tech.

If you're working a dropdown that requires three different clicks to get there, it's pretty neat to be able to change the code and watch the UI change without having to reload and get the UI back into a state where you can see the effects of your change.

While this is not exclusive to React, the React component architecture lends itself very well to hot module reload. Dan Abramov, creator of Redux, React Drag-and-Drop, and other great stuff also wrote the code for this. He's a magician.

How does it work? Seems magical. Well, since we're using ES6 modules, it means the dependency graph of our app is static. Think of it as a big tree with branches. If one branch changes, it means we can tear off the branch (the old code) and graft on a new one (the new code that Webpack just compiled) without stopping the rest of the code from running

    Landing
   /
App
   \
    Search — ShowCard

That's our app as it stands in terms of dependencies. App imports Search which imports ShowCard. If Landing changes, we can leave App, Search, and ShowCard running as is and just graft on a new Landing. If Search changes, we have to graft on a whole new Search which includes new ShowCards.

This is accomplished with some black magic from Webpack and Babel. Babel when it transforms your code from JSX to JS and from ES6+ to ES5 will also instrument your modules with the ability to be replaced. We'll then insert a small client into our code that will receive small JSON packages via websockets and insert those into our running code. None of these details are important: mostly it's just for your information. react-hot-loader, Webpack, and Babel largely abstract these away from you.

Let's start with your .babelrc file. Add this as a top level property:

"plugins": [
  "react-hot-loader/babel"
],

This is what instruments the code with the ability to be replaced. Refactor your webpack.config.js to look like this:

const path = require('path');
const webpack = require('webpack');

module.exports = {
  context: __dirname,
  entry: [
    'react-hot-loader/patch',
    'webpack-dev-server/client?http://localhost:8080',
    'webpack/hot/only-dev-server',
    './js/ClientApp.jsx'
  ],
  devtool: 'cheap-eval-source-map',
  output: {
    path: path.resolve(__dirname, 'public'),
    filename: 'bundle.js',
    publicPath: '/public/'
  },
  devServer: {
    hot: true,
    publicPath: '/public/',
    historyApiFallback: true
  },
  resolve: {
    extensions: ['.js', '.jsx', '.json']
  },
  stats: {
    colors: true,
    reasons: true,
    chunks: false
  },
  plugins: [new webpack.HotModuleReplacementPlugin(), new webpack.NamedModulesPlugin()],
  module: {
    rules: [
      {
        enforce: 'pre',
        test: /\.jsx?$/,
        loader: 'eslint-loader',
        exclude: /node_modules/
      },
      {
        test: /\.jsx?$/,
        loader: 'babel-loader'
      }
    ]
  }
};
  • We required Webpack. We need to pull some plugins off of it.
  • We added some additional files to be packed into app via the entry property. The order here is important.
  • We gave the output a publicPath so it can know where the bundle.js file is since that's where it'll pull new bundles.
  • We told the dev server to run in hot reload mode.
  • We gave Webpack two plugins to work with. These affect the internals of Webpack. The second one is optional but it's helpful because it'll print the name of the files being hot reloaded.

From here we need to split the App component out of ClientApp. We need to give hot module reload the ability to split the root component away from what actually renders the component to the page. Turns out we would have had to do this anyway for server-side rendering anyway so it's fine for us to tackle this now.

Create a new file called App.jsx and put this in there:

import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
import Landing from './Landing';
import Search from './Search';

const App = () => (
  <BrowserRouter>
    <div className="app">
      <Route exact path="/" component={Landing} />
      <Route path="/search" component={Search} />
    </div>
  </BrowserRouter>
);

export default App;

Then from here, make your ClientApp.jsx look like this:

import React from 'react';
import { render } from 'react-dom';
import App from './App';

const renderApp = () => {
  render(<App />, document.getElementById('app'));
};
renderApp();

if (module.hot) {
  module.hot.accept('./App', () => {
    renderApp();
  });
}

The first thing I always here is "Am I shipping that module.hot stuff down in prod!?" Yes, you are, and no, it doesn't matter. It's an if statement that gets runs once. You'll be fine. If you're so worried, use something like groundskeeper.

Okay, so we made renderApp a function so that we can render the App each time App itself changes. The rest of the modules will take care of themselves: this is all you need to hook up to get hot module reload to work! Also, when you build for production, all of the hot module reload code and transformations will be stripped out, meaning you're not making your package any larger (other than the if statement in ClientApp). Go give it a shot! Open your console (so you can see the debug statements) and go change something in your app. You should see the UI change without a full page reload. Pretty cool!

·

Flow: Types in JavaScript

Welcome to the wild and mystical world of types in JavaScript. Types seem intimidating to many people but with a few basic concepts under our belt we can take advantage of 80% of the benefits. In other words, most of the beneftis of a type checker come from doing the most basic things. You can next-level crazy with types (see Haskell and Idris) but here we're just, you know, going with the flow.

So what is a type? JavaScript has a few basic types that it uses: Number, String, Object, Array, Function, and a few others. Because an item is a String, there's certain things you know about it: you can always call substr on a String and it won't cause an error. However, if you try to call substr on a Number, you're going to get an error. At the most basic level, that's what a type checker like Flow is going to get for you. It's going to prevent those sorts of errors by informing you that you have a type mismatch.

What's magical about Flow is that most of this type checking is free. You don't have to do anything to get it. You don't have to declare the type! It just knows what type is should by how you initialize it. If I say const x = 'string here' it knows that x is a string; you don't need to tell it that. Only in certain situations do you need to inform Flow of the types.

So, let's get Flow into our code. First thing to do is run yarn run flow -- init. You can also install Flow globally and do that, but this is nice because it'll run flow from your dependencies. This will create a .flowconfig file for you which is often enough; often you don't need to customize that config at all. In my case today, styled-components is throwing errors that isn't useful so I'm going to add <PROJECT_ROOT>/node_modules/styled-components/* under [ignore]. Unlike ESLint, you do typically want to have Flow run on your dependencies to get those typings. However sometimes libraries ship broken types so you can just ignore them (like I did with styled-components.)

Luckily for us, there's flow-typed. flow-typed is a repo of pre-typed common libraries for you and styled-components has an entry in there. Just yarn global add flow-typed and then run flow-typed install styled-components@1.4.6 (or whatever version you have.) This will create a flow-typed directory that Flow already knows how to read from. In fact, check out the various type available to you. You should be able to grab types for enzyme, jest, react-router and react-router-dom too. Flow already has typings for React and react-dom.

Now you should be able to do yarn run flow. This likely isn't going to give you any errors back because we haven't opted-in any of our files to be type checked. Let's opt in Search.jsx. Add the pragma // @flow to the top of Search.jsx. Now run yarn run flow again. You should see an error that handleSearchTermChange is not annotated. Since Flow doesn't know how handleSearchTermChange is going to be invoked and with what parameters, it requires you to tell it beforehand. We do this by doing a type annotation.

Since we're including the Babel preset for React, we actually already are getting the Flow plugin. If we hadn't included babel-preset-react We'd have to go include the Flow plugin to do annotations. But it's there already.

So change in Search.jsx:

handleSearchTermChange = (event: SyntheticKeyboardEvent &  { target: HTMLInputElement }) => {
  this.setState({ searchTerm: event.target.value });
}

What we've done here is add a type for the incoming parameter: a synthethic (React) keyboard event. This type is already built into Flow so we don't have to do anything more with it. Specifically we're interested win the target portion of it, so we identify that as well (target can technically be things other than an HTML input element.) By doing this we're ensuring we'll be ensuring this method is used properly. Run yarn run flow again and you should see No errors!. Congrats!

We've also made it so handleSearchTerm must be called with a SyntheticKeyboardEvent item. If you try to invoke it without that being passed in, you'll fail when you run Flow. You can use maybe types to get around this.

What's cool is that despite not adding a lot, we actually opted in a lot of behind-the-scenes checking for free. For example, Flow is smart enough to know that searchTerm is a string based on the initial state we provide for the class. Go ahead and try and setState for searchTerm to be a number. Flow will now fail since it was expecting a string there. If you need to accommodate both numbers and strings, you can use what's called a union type.

It also bears talking about [any types][any] as well. Any types are both horrible and wonderful. Bascially, anything you make an any type is opted out of type checking. This can be great if you're rapid prototyping something and you're not sure what the final API is going to look like and you don't want to be fiddling with types (however, I will say that fiddling with the types can yield great insights into what you're actually trying to do!) They can also be useful for migrating large codebases to Flow: make big things an any type until you can get around to typing that particular object. Anything that you import from npm that you don't have typings for will by default be an any type. Also note there are mixed types that are useful if you're not sure what sort of thing is being passed in. Use this when possible instead of an any type.

At this point it's useful to see if your editor has integration with Flow. Sublime's is lackluster but you can at least get it show you where you have Flow errors in real time (using the same SublimeLinter we used for ESLint.) If you're going to double-down on Flow, you may investigate Nuclide. It's a package for [Atom][atom] that integrates with all of Facebook's tooling like React, React Native, Hack, and Flow. For now I'll be using just the Sublime tools.

Let's go opt-in ShowCard.jsx too! Add the // @flow pragma to the top. Here we've added propTypes from the prop-types library and with Flow those no longer serve a purpose: these types are known at compile time and thus don't need to be checked again at run time. Thus let's modify ShowCard like so:

// delete prop-types import

// replace ShowCard
const ShowCard = (
  props: {
    poster: string,
    title: string,
    year: string,
    description: string
  }
) => (
  <Wrapper>
    <Image alt={`${props.title} Show Poster`} src={`/public/img/posters/${props.poster}`} />
    <div>
      <h3>{props.title}</h3>
      <h4>({props.year})</h4>
      <p>{props.description}</p>
    </div>
  </Wrapper>
);

This is how you type functional components. Pretty cool, right? Now if you run Flow again you shouldn't see any issues. On a tangent, imagine showing that code to yourself pre-ES6. Doesn't even look like (old) JavaScript! But's powerful, expressive, and readable (in my opinion.)

You should be able to // @flow to the top of Landing and App and make no changes.

Add // @flow to the top of ClientApp.jsx. You'll start getting errors about module.hot which is a global variable given to you by Webpack. Right now (as of writing) flow-typed doesn't have types for Webpack's API but this can be a useful exercise in showing you how to define your own types. In your flow-typed folder, add a file called types.js (not in the npm directory.) Add the following:

// @flow

declare var module: {
  hot: {
    accept(path: string, callback: () => void): void
  }
};

Here we've declared a global variable called module which is an object that has a method called accept. accept takes two parameters: a string called path and a function called callback which returns undefined. accept itself returns undefined. There are several things you can put into these type definition files but I'll let you explore that on your own time!

Now our whole project is typed! We'll continue using Flow for the rest of this project but by no means are types required for React or any sort of JavaScript. It is extremely useful to have; you'll see here as you write code it's very useful to have the instant feedback that something is wrong with your code; once you start getting that it's hard to go back. But yeah, again, this is just one tool you could use to write React. It's not required.

·

Data in React

So now we want to add a third page: a page where we can watch the trailer. This is going to be an application of what we know already.

Create a new file in js/ called Details.jsx. In Details put:

// @flow

import React from 'react';

const Details = () => (
  <div className="details">
    <h1>lolhi</h1>
  </div>
);

export default Details;

In App.jsx, put your new route:

// require your new route
import Details from './Details'

// add as last route in the nested routes
<Match pattern='/details/:id' component={Details} />

Here we've added a URL parameter using the colon. Now as a prop to Details you'll get whatever :id was. So now try to manually change the anchor on your url to be http://localhost:8080/#/details/1. You should see your new component here.

If you see a blank page and a 404 error in your console, chances are you need to add a leading slash to your script and link tags in your index.html file for the paths, <&NegativeMediumSpace;script src="/public/bundle.js"></script> and <&NegativeMediumSpace;link rel="stylesheet" href="/public/style.css" />.

Let's show you a neat debugging tip I totally stole from Ryan Florence. replace that h1 with this:

// instead of the h1 in render
```javascript
const Details = props => (
  <div className="details">
    <pre>
      <code>
        {JSON.stringify(props, null, 4)}
      </code>
    </pre>
  </div>
);

This is a useful way to dump your params to the page to see what react-router is giving you. This is awesome for state too; it shows you in real time what your state looks like. We'll dig into React Tools here in a sec for even better debugging but for now let's keep trucking with our Details.jsx.

We're going to show all the details of a show on this page and be able to play the trailer. There's a big problem here that we don't have that data on this page though; it's available in the Search route. We could require in data.json here given that our data is available that way but that typically isn't the case: we typically get this data from the server. If that's the case, you don't want to make two AJAX requests to get the same data. In other words, we need to share this state between components. The way you do this is by pushing up the state to the highest common ancestor component. In this case, that'd be the router in App. So let's first refactor Search to still work while it pulls in that data from Search.

// in App.jsx
// another import
import preload from '../data.json'

// modify the existing route
<Route path="/search" component={props => <Search shows={preload.shows} {...props} />} />

Now make Search use it

// delete import preload from '../data.json'

// below imports, above class Search

type Show = {
  title: string,
  description: string,
  year: string,
  imdbID: string,
  poster: string,
  trailer: string
};

// add propTypes inside Search
props: {
  shows: Array<Show>
};

// change the map call instead of {preload.shows
{this.props.route.shows

Cool. Now it should still work but Search no longer imports the data but merely receives it as props. Now we can share this data with Details. Notice that Search has a function instead of a React component, but if you think about it a function that returns markup is a React component, so this works. This allows us to pass in the shows as a parameter to Search. You'll see this pattern often with react-router v4.

You'll also notice we created a Show type to match our show data. This is called a type alias This is awesome because now we can refer to objects as Shows and get all the typing along with that. Our first use of it was specifying that the props passed down was going to be an array of Shows. The syntax with Array has to do with a concept called generic types which you are welcome to read about but beyond the scope of this class. You can do really cool and clever things with types and it's worth a dive down the rabbit's hole.

This Show type is going to be used across multiple files so it's worth it to make it a project-wide type like we did with Webpack's module. Go add to the types.js file in flow-typed. Add declare before type so it says declare type Show = { […] so it's now a global. Remove it from Search.jsx. Everything should still work. It should look like this:

export type Show = {
  title: string,
  description: string,
  year: string,
  imdbID: string,
  poster: string,
  trailer: string
};

Do note with with Flow and ESLint integrations with editors, it can be slow to update. It can be frustrating when you think you fixed a problem and it hasn't resolved yet.

Now we're going to pass the correct show to the Details page. There's a bunch of ways to do this:

  • We could pass all the shows and let Details select the correct show. This isn't great because Details is given an additional concern it doesn't need to have.
  • We could create a callback in App that it passes to Details that Details calls with the correct ID and ClientApp hands back the correct show. This is slightly better but it's an odd API for getting data. Ideally we just hand down props and not a callback, especially since this isn't async.
  • Or we could hook into react-router's ability to pass props down through stateless functions like we did with Search and just pass down the correct show. This is the best approach.

Add the following to App:

// add at the imports at the top
import type { Match } from 'react-router-dom';

// replace Details Match component
<Route
  path="/details/:id"
  component={(props: { match: Match }) => {
    const selectedShow = preload.shows.find((show: Show) => props.params.id === show.imdbID);
    return <Details show={selectedShow} {...props} />;
  }}
/>

At the top we're importing types from types from the flow-typed file for react-router-dom. We'll use this type to refer to the match attribute of the props. This is how you import types if you need to in the future. In case it's apparent to you, this a Flow-specific feature; this import line of code gets stripped out by the Babel transform. In the code, we're finding the correct show and passing that to Search.

If you run flow, you'll notice we broke our tests. Here is yet another benefit of Flow: it'd be easy to forget how modifying the API for Search would break the tests. Flow is quick to get that. It derives that fact because we changed what props are being passed to Search. Clever!

This should put the correct show as one of the props that App passes down to Details. If you refresh the page, you should see it now. (You have to have a valid URL for a details page, like <your localhost>/details/tt4574334).

As an aside, I've found the best way to organize React method component is the following

  1. props / defaultProps/ props
  2. constructor
  3. Other lifecycle methods like componentDidUpdate (we'll talk about those in a sec)
  4. Your methods you create (like assignShow)
  5. render

Makes it easier to find things when you look for them.

So let's actually display some cool stuff:

// @flow

import React from 'react';

const Details = (props: { show: Show }) => {
  const { title, description, year, poster, trailer } = props.show;
  return (
    <div className="details">
      <header>
        <h1>svideo</h1>
      </header>
      <section>
        <h1>{title}</h1>
        <h2>({year})</h2>
        <img src={`/public/img/posters/${poster}`} alt={`Poster for ${title}`} />
        <p>{description}</p>
      </section>
      <div>
        <iframe
          src={`https://www.youtube-nocookie.com/embed/${trailer}?rel=0&amp;controls=0&amp;showinfo=0`}
          frameBorder="0"
          allowFullScreen
        />
      </div>
    </div>
  );
};

export default Details;

Now you should have some nice looking UI.

Well, now we have a header in two places. That's a strong indicator that you should make it's its own component. Let's abstract that in a component and use that in both places. Create a new file called Header.jsx and put this in there:

import React from 'react';
import { Link } from 'react-router-dom';

const Header = () => (
  <header>
    <h1>
      <Link to="/">
        svideo
      </Link>
    </h1>
  </header>
);

export default Header;

We're even going to throw in a link back to the home page for fun. Now open Details.jsx and put:

// add to the top
import Header from './Header'

// replace <header>...</header>
<Header />

Let's put a back button on the Header so you can get back to Search after you reach it.

// after the h1 inside .header
<h2>
  <Link to='/search'>
    Back
  </Link>
</h2>

So let's integrate this to Search. But it's not so simple since on Search we want the header to have a search input and on Details we want a back button. So let's see how to do that. In Header.jsx put:

// @flow

import React from 'react';
import { Link } from 'react-router-dom';

const Header = (props: { showSearch?: boolean, handleSearchTermChange?: Function, searchTerm?: string }) => {
  let utilSpace;
  if (props.showSearch) {
    utilSpace = (
      <input type="text" placeholder="Search" value={props.searchTerm} onChange={props.handleSearchTermChange} />
    );
  } else {
    utilSpace = (
      <h2 className="header-back">
        <Link to="/search">
          Back
        </Link>
      </h2>
    );
  }
  return (
    <header>
      <h1>
        <Link to="/">
          svideo
        </Link>
      </h1>
      {utilSpace}
    </header>
  );
};

Header.defaultProps = {
  showSearch: false,
  handleSearchTermChange: function noop() {},
  searchTerm: ''
};

export default Header;

In Search.jsx:

// add to requires
import Header from './Header';

// replace <header></header>
<Header handleSearchTermChange={this.handleSearchTermChange} showSearch searchTerm={this.state.searchTerm} />

This is how you have a child component modify a parent's state: you pass down the callback and let it call the parent to let the parent modify the state. This also demonstrates how to conditionally show one component and not another.

Lastly let's make our show cards clickable.

// @flow

import React from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';

const Wrapper = styled.div`
  width: 32%;
  border: 2px solid #333;
  border-radius: 4px;
  margin-bottom: 25px;
  padding-right: 10px;
  overflow: hidden;
`;

const Image = styled.img`
  width: 46%;
  float: left;
  margin-right: 10px;
`;

const ShowCard = (
  props: {
    poster: string,
    title: string,
    year: string,
    description: string,
    imdbID: string
  }
) => (
  <Link to={`/details/${props.imdbID}`}>
    <Wrapper>
      <Image alt={`${props.title} Show Poster`} src={`/public/img/posters/${props.poster}`} />
      <div>
        <h3>{props.title}</h3>
        <h4>({props.year})</h4>
        <p>{props.description}</p>
      </div>
    </Wrapper>
  </Link>
);

export default ShowCard;

Oh 💩! We messed up our styles. The reason is that the way Link works is that it outputs an <a> tag. Luckily, we can even style that too! Try this:

// replace styled.div with styled(Link)
const Wrapper = styled(Link)`

// add two lines to Wrapper's CSS, otherwise you'll get blue text styles
text-decoration: none;
color: black;

Now each of the cards should be clickable through to the details page and styled correctly!

But now we've messed up Search's tests. If you remember, Enzyme's testing library only shallowly renders the components. Since we moved the Header logic from inside Search into Header, this is going to mess up the existing snapshot (which we can just fix easily) and we won't be able to directly interact with the input inside of Header without some additional code.

We also changed the contract of Search since it now requires the shows to passed in. Modify the three <Search /> to be <Search shows={preload.shows} />.

First, run yarn test:update to fix your snapshot. Your third test will still fail but the first one will update.

Next, open Search.spec.jsx and add this:

// last import
import Header from '../Header';

// modify the simulation line
component.find(Header).dive().find('input').simulate('change', { target: { value: searchWord } });

By finding and "diving" on the Header component, we're telling Enzyme to also render that Header so that we can interact with it. Run your tests again and now they should pass!

·

React Lifecycle Methods and AJAX with React

Due to the structuring of our app, we haven't had to use React lifecycle methods despite the fact they're fairly common to use and thus important to know. One of the most compelling reasons to use lifecycle methods is to do AJAX. Once a component gets mounted to the page then we want to be able request data from the server. First let's discuss the lifecycle of a React component.

  1. constructor/getInitialState - This is where set up your components initial state. The former is for ES6 classes (which is what we've been doing) and the latter is for the React.createClass method (which is deprecated as of 15.5.)
  2. getDefaultProps - Often you want to give your components default props if the parent doesn't provide them. Have a button that you want to be able to be a variety of colors but want default to green? That's what you would put in here. In ES6 classes, this can just be static property that's an object of the default props.
  3. componentWillMount - This method runs right before the component gets mounted. This one is not too common to use, but you will want to use it any time you want to ensure code to run both in node and in the browser.
  4. componentDidMount - This method runs right after your component gets put into the DOM. This method will not get run in node but will in the browser. This makes it so your component can render first then you can go get the data you need. In your component you can throw up a loader if you need to. Also if you need to interact with the DOM (like if you were wrapping D3 or a jQuery plugin) this would be the place to do it.
  5. componentWillReceiveProps - This method runs every time the React component receives new/different props from the parent. If some of the state you keep in your component is derived from the parent props, this is where you would take care of that. What if you keep a list of actors in a movie as state that you request from an API? If your parent passes you a new movie, you need to react to that and get new actors for the new movie. This would be an example of where to use this method.
  6. shouldComponentUpdate - This method returns a boolean letting React know if it should re-render the component. This is for performance purposes. If you have a component that will never update (like a static logo or something) you can just return false here. Normally React is really fast at doing this diffs anyway so it's a good idea to only put in a shouldComponentUpdate method if it's actually a performance issue. Typically in the method body you would check the bare minimum of state that needs to have changed to warrant a re-render. We'll discuss this more in depth later.
  7. componentWillUnmount - This method runs right before the component is taken off the DOM. Most common thing to do here is get rid of external event listeners or other things you need to clean up.

Cool! So let's make our Details page get the details from a server! First let's make a nice loading spinner. Make a new file called Spinner.jsx and put this in there:

// @flow
import React from 'react';
import styled, { keyframes } from 'styled-components';

const spin = keyframes`
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
`;

const Image = styled.img`
  animation: ${spin} 4s infinite linear;
  background-image: url(/public/img/loading.png);
`;

const Spinner = () => <Image src="/public/img/loading.png" alt="loading indicator" />;

export default Spinner;

This is how you do keyframe animations with styled-components. It's really cool because you'll get scoped animation and not have to worry about some polluted global namespace.

// import in axios
import axios from 'axios'

// add propType inside show
imdbID: string

// add state and componentDidMount to Details
 state = {
  apiData: { imdbRating: '' }
};
componentDidMount() {
  axios
    .get(`http://localhost:3000/${this.props.show.imdbID}`)
    .then((response: { data: { rating: string } }) => {
      this.setState({ apiData: response.data });
    });
}

// add to render before return
let rating;
if (this.state.apiData.rating) {
  rating = <h3>{this.state.apiData.rating}</h3>;
} else {
  rating = <Spinner />;
}

// add between year and poster
{rating}

We used to use the open movie database to request ratings against but it went closed. Rather than make your signup, there's a tiny API server for you to run with ratingsAPI.js. Just run node ratingsAPI.js from the commandline and let it run in the background. Now you'll be able to make requests against it. Feel free to add a line to your scripts object in package.json: "api": "node ratingsAPI.js" so you can just run yarn api to run the server.

We're requiring in axios which is a great little promise-based AJAX client and using that to make requests to the our little API server to find the IMDB ratings. If you go to your pages now you'll notice that the rating is showing up a little after the page renders. As you can see, we did this componentDidMount so that the user could see UI before waiting on an AJAX request. Note that it won't get server-side rendered either because the server doesn't call componentDidMount.

That's it! That's all you need to need know about AJAX with React as well as the lifecycle methods!

·

React Tools

So far we've been using pretty old school debugging technology: console.logs and just dumping stuff out to the DOM. There is an easier way! React Dev Tools! Grab it here for Chrome and Firefox. In theory you can get the Chrome version working on Microsoft Edge, but good luck. If you're not using Chrome or Firefox, you're out of luck for now. They're talking about doing a standalone app but we'll see when that finally surfaces. I'll be talking about the Firefox version because that's the one I know the best but this should apply to Chrome just as well.

Dev Tools allow you to explore the virtual DOM of React as if it was just a normal DOM tree. You can see what props and states are in each component and even modify the state of components. The virtual DOM explorer is by-far the most useful part of it.

Find the Dev Tools in Firefox by opening the dev tools and the last tab (along side Console, Sources, Network, etc.) should be React on your page. If you don't see it try restarting your browser. If you still don't see it, the tab won't show up if the extension can't detect React on the page. You may have a dated version of the dev tools. After that I'm not sure; it can be fickle sometimes.

Feel free to poke around a bit here to familiarize yourself with what the React Dev Tools can do. Here are a couple of tricks for you:

  • If you right-click -> Inspect Element on something and then click the React tab, it will take you straight to that element in the virtual DOM.
  • Select something in the virtual DOM in the React tab. Now go to the Console tab and type $r. It should be a reference to the element you have selected in the virtual DOM and you can manipulate it.
  • As a side note, you can do the above trick with the normal DOM explorer with $0, $1, $2, $3, and $4 instead where 0 is the last one you selected, 1 is the penultimate, etc. This is true in both Chrome and Firefox
  • iframes and Chrome/Firefox extensions don't work with React Dev Tools as of writing.
  • react-router v4 has a lot of nesting.
·

React Perf Tools

As you app continues to grow, sometimes slow code paths can start to emerge. React is pretty good about being performant under normal loads but even React has its limits. One of the ways this can manifest itself is how often components are running through the re-render process without actually changing anything. These are called wasted renders. Normally you don't have to actually to worry about this: React handles a lot of wasted re-renders without slowing down the app at all and you can spend your time building features instead. It's helpful to glance at your performance data every once in a while but don't dwell too much on it.

So, as it stands, our code actually does have a code path that we can optimize. So let's go explore how we discover the problem and ultimately fix it.

First, open ClientApp.jsx and add:

// before App import
import Perf from 'react-addons-perf';

// after App import
window.Perf = Perf;
Perf.start();

This is temporary code. You do not want to ship this in production. We'll use this to profile our code, get the useful info out of it and then remove it since it'd just be dead bloat in production.

So what we've done is import the React perf tools which will automatically hook into your React instance and track wasted renders for you. We're just setting it on the global window object so we can manipulate it directly in the console.

Now open your browser, and navigate around you webpage a bunch. Visit all the various ShowCard components, visit the home route, enter some search terms, delete the terms, enter different ones, etc. You want to cause the UI to change in all the various ways that are possible so we can see the slow code paths.

After you've sufficiently prodded your app, open you console and enter: Perf.stop() followed by Perf.printWasted(). You see a console table of the various wasted renders sorted by how many milliseconds are being wasted. Based on this you can find hot code paths and see what you can do to fix tihs.

In our case, it jumps out that our ShowCard is the greatest offender here: it's a component that once rendered never changes. There's nothing dynamic about any individual ShowCard. Armed with this information, there's an easy optimization to make here: shouldComponentUpdate.

Open ShowCard.jsx. Right now it's a function component so we can't add a lifecycle method like shouldComponentUpdate so we'll have to convert it to a class. Change the component to be:

class ShowCard extends React.Component {
  shouldComponentUpdate() {
    return false;
  }
  props: {
    poster: string,
    title: string,
    year: string,
    description: string,
    imdbID: string,
  };
  render() {
    return (
      <Wrapper to={`/details/${this.props.imdbID}`}>
        <Image alt={`${this.props.title} Show Poster`} src={`/public/img/posters/${this.props.poster}`} />
        <div>
          <h3>{this.props.title}</h3>
          <h4>({this.props.year})</h4>
          <p>{this.props.description}</p>
        </div>
      </Wrapper>
    );
  }
}

Now whenever React goes to see if an individual instance of a ShowCard has changed, it'll run this function instead of running the render function and diffing against the last run of the render function. As you may imagine, this will go a lot faster.

Let's talk about the mechanics of shouldComponentUpdate. Almost all of the times I personally have used it, it has been just what you've seen here: returing false to ensure a component that doesn't needs to update doesn't go through the diffing process. There is a more advanced use case where perhaps updates only rely on a few pieces of state, props, or anything else. Here in shouldComponentUpdate, you could check those and only re-render if those changed. See the React docs for more info.

Go back and profile your App again with the perf tools. You'll notice that ShowCard has disappeared from it.

This is a powerful and potentially bug-causing tool for you. Fathom later that we had dynamic content to ShowCard. If you call setState, nothing will happen until you remove or modify that shouldComponentUpdate method. Many people once they learn about shouldComponentUpdate will put it everywhere: don't. You'll cause more issues than you'll solve trying to solve perf problems that don't exist. React is normally fast enough as-is.

·

Redux

The next thing we want to do with our app is make the front page's search work so that when you type in a search query and hit enter it will automatically have searched for that on the Search page. Right now you have all the necessary tools to do that via state. You could just push the query term up to the ClientApp level and then pass that down to the Search and you'd be done. And that's the way you should do it given how small our app is.

But when these demo apps all the fun is in over engineering it and that's precisely what we're going to do: we're going to add Redux. Redux is a fantastic tool and a cool blending of the ideas of Facebook's Flux and the Elm architecture.

As a side-note, there are some super rad new tools out there like [Mobx][mobx] that you can check out, but we're sticking to Redux. Mobx is incredible but with more power comes more complexity. If you learn Redux then learn Mobx (and reactive programming) you'll really appreciate and/or fear the power that comes from Mobx.

So what is Redux? Redux is a predictable state container for JavaScript apps. The best part about it while the concept is at first hard, I'd argue it's also very simple and elegant. Redux is great because it will run both client and server side, it's easy to test, and easy to debug. While Redux does not not follow the Flux pattern, you can easily see the similarities and once you've done one the other isn't hard to adapt to.

With Redux you a single store which stores your entire app state in a single tree. This is not like Flux where you'll have many stores for many different parts of your app; all data lives in a single store. You cannot directly modify the tree of data stored in this tree by typical assignment (ie tree.prop = 'foo' doesn't work.) Rather, every time you want to modify the tree, you emit an action. Your action then kicks off what's called a reducer. A reducer is a special function that take a tree and parameter(s) and returns a new tree after applying whatever transformations it deems fit. The way it gets away with just one store is when you need more data you just add more branches to your data tree. Like React? You only have one tree of components and when you need more you just add more nodes (branches) to your components.

So let's do the most basic addition of Redux to our app and convert the Search to use Redux. Again, this is using a sledgehammer to solve a tiny nail problem: huge overkill.

Create a reducers.js, put this in there:

const DEFAULT_STATE = {
  searchTerm: '',
};

const rootReducer = (state = DEFAULT_STATE, action) => {
  switch (action.type) {
    default:
      return state;
  }
};

export default rootReducer;

Create a store.js and put this:

const DEFAULT_STATE = {
  searchTerm: '',
};

const rootReducer = (state = DEFAULT_STATE, action) => {
  switch (action.type) {
    default:
      return state;
  }
};

export default rootReducer;

This is about as bare bones as Redux gets: we boot strapped a Redux store with a single top-level reducer and exported that. One thing you're going to find with Redux is there's a long path to follow to follow how your state changes. A very predicatble and consistent path, but it's still way longer than it used to be when we were just dealing with React state. This will often not be worth it. Evaluate this yourself on a per-project basis.

So like we said, each store starts with one reducer: the root reducer. This root reducer in turn will dispatch to other reducers. A few keys to notice here:

  1. You must return the finished state each time.
  2. You must handle action types you've never seen before (which why we have the default clause.)
  3. You take in state, you copy it, and you return a new state. That's what any reducer does. If you return the same state, Redux thinks nothing happened and won't inform React of any changes.
  4. You must have a default state.
  5. Redux by itself has no way of dealing with async actions. You need to pull in another library like redux-thunk. We'll use that later.

We haven't opted into Flow yet. We will. I want to show you what Redux is doing at its core before we get clever.

Okay make a new file called actions.js and put in there:

// @flow
export const SET_SEARCH_TERM = 'SET_SEARCH_TERM';

This is going to give you an eslint error for prefer defaults exports when there's only one export. Generally this is a good idea but we're going to be adding more exports here momentarily.

Create a file called actionCreators.js:

// @flow
import { SET_SEARCH_TERM } from './actions';

export function setSearchTerm(searchTerm) {
  return { type: SET_SEARCH_TERM, payload: searchTerm };
}

You'll see the same ESLint export error. Ignore for now.

We're using the flux standard action shape of actions for our Redux actions. This isn't required. The idea here is that we all adhere to this standard, this actions can be easily ported amongst Redux, Flux, and other state management libraries with ease. In any case, it'll make working Flow easier, which is the real reason.

Now back to reducers.js:

// import at top
import { SET_SEARCH_TERM } from './actions';

// new reducer above rootReducer
const setSearchTerm = (state, action) => {
  return Object.assign({}, state, {searchTerm: action.payload});
}

// add new case before default inside rootReducer
case SET_SEARCH_TERM:
  return setSearchTerm(state, action);

More files! This should be it for our simple project. Actions is just going to a bunch of exporting of constants. Why do we do this? The way Redux's root reducer decides to dispatch it to one of various reducers is by the action type. Thus it needs to match in both the action creator and the reducer. Rather than having magic strings, we have one central source of truth both file read from. Makes refactoring easy too.

I often get asked why we do make the actions strings and not symbols. While it does work, the dev tools are unable to serialize symbols and thus we makes it much harder to debug. Maybe some day. We'll look at the dev tools in a bit.

The actionCreator is what the UI is actually going to interact with to make changes to the Redux store. In other words, your UI never directly interacts with the store nor the reducers. It only interacts with action creators which then are handled in the reducers which then change the store which then inform the UI of the changes. One way data flow!

If you haven't seen the syntax const x = { searchTerm } it just means const x = { searchTerm: searchTerm }. It's just a shortcut.

The rootReducer uses the same SET_SEARCH_TERM constant to hinge in the rootReducer. Also note we return a new object every time when we make a new object. This lets Redux know to inform any subscribers (in this case your React app) that changes happened.

Okay, so let's go make landing interact with the store. But first we need to connect Redux to React via the react-redux package. Go to App.jsx.

// import react-redux and your new store
import { Provider } from 'react-redux';
import store from './store';

// wrap everything in router in provider
render () {
  return (
    <BrowserRouter>
      <Provider store={store}>
        […]
      </Provider>
    </BrowserRouter>
  )
}

Provider connects React to Redux for you. Now you can magically use a connect function (also provided from react-redux) that allows you to pull in the pieces of state you need in each component. Let's got make Landing.jsx read and write to Redux.

// @flow

import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';

const Landing = (props: { searchTerm: string }) => (
  <div className="landing">
    <h1>svideo</h1>
    <input value={props.searchTerm} type="text" placeholder="Search" />
    <Link to="/search">or Browse All</Link>
  </div>
);

const mapStateToProps = state => ({
  searchTerm: state.searchTerm
});

export default connect(mapStateToProps)(Landing);

Connect is a function that allows your component to tap into the Redux store's state. The mapStateToProps allows you to select which pieces of state are passed into your component which helps keep thing clean. At the bottom we export a connected version of the component. Now if you reload the page the input doesn't work for the same reason it didn't with React previously: we are never sending the typed text to Redux to update its state. Let's do that now.

// at top
import { setSearchTerm } from './actionCreators'

// add type for function
const Landing = (props: { searchTerm: string, handleSearchTermChange: Function }) => (

// change input
<input onChange={props.handleSearchTermChange} value={props.searchTerm} type="text" placeholder="Search" />

// at the bottom
const mapDispatchToProps = (dispatch: Function) => ({
  handleSearchTermChange(event) {
    dispatch(setSearchTerm(event.target.value));
  }
});

export default connect(mapStateToProps, mapDispatchToProps)(Landing);

We're import the action creator so that we can dispense well-formed actions to Redux. Technically you could form the action here inside of the dispatch function but it's a good idea to separate that logic so that it can be re-used and individually tested.

In addition to adding the state to the props via mapStateToProps, we also want to inject a function which can dispatch actions to your reducers. We do this via a mapDispatchToProps function which achieves a similar end.

At the end, make sure you add that to the connection function.

After this, we want to be able to send the user to the search page once they hit enter. We'll do this via interacting with react-router imperatively.

Since we'll be introducing some methods, we also should refactor this into ES6 class component. It'll make it easier to follow. We've outgrown the component function.

// @flow

import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import type { RouterHistory } from 'react-router-dom';
import { object } from 'prop-types';
import { setSearchTerm } from './actionCreators';

class Landing extends React.Component {
  static contextTypes = {
    history: object
  };
  props: {
    searchTerm: string,
    handleSearchTermChange: Function,
    history: RouterHistory
  };
  goToSearch = (event: SyntheticEvent) => {
    event.preventDefault();
    this.props.history.push('/search');
  };
  render() {
    return (
      <div className="landing">
        <h1>svideo</h1>
        <form onSubmit={this.goToSearch}>
          <input
            onChange={this.props.handleSearchTermChange}
            value={this.props.searchTerm}
            type="text"
            placeholder="Search"
          />
        </form>
        <Link to="/search">or Browse All</Link>
      </div>
    );
  }
}

const mapStateToProps = state => ({
  searchTerm: state.searchTerm
});

const mapDispatchToProps = (dispatch: Function) => ({
  handleSearchTermChange(event) {
    dispatch(setSearchTerm(event.target.value));
  }
});

export default connect(mapStateToProps, mapDispatchToProps)(Landing);

So we're introducing a new concept here from React: context. This is a dangerous tool and I will tell you I personally have never put anything on context. I've only consumed things from context that libraries like react-router and react-redux (which both do use context) put on there. Use at your own peril. It will cause more harm than solve.

Context is basically global state: anywhere inside a React app can read and write to state. If this sounds nightmarish to you then you have good sense: it defeats a lot of the benefits to React. However, with something like react-router it's very useful because the whole app does care about routing, as it does about Redux.

Notice the contextTypes are like propTypes. However, contextTypes are even more important to React than propTypes: if you don't have them the object you're looking for won't be there. In other words, you must identify in contextTypes the properties the component cares about or they will not be available on context. This ongoing debate on how this will work in the future since prop types have been removed from the React package itself.

Also note that contextTypes property is static. This is important so that React can read the types off the class instead of off the instance.

Okay, so now I want to show you a neat experimental feature: decorators. This is 1000% optional. What we have works and you are welcome to stick with it. I just think they're fun to use and make the code a bit nicer to read. Add the plugin "babel-plugin-transform-decorators-legacy" to your .babelrc before the class-properties one. The order is important.

// in Landing.jsx

// move mapStateToProps and mapStateToDispatch to above the class

// add above the class
// $FlowFixMe
@connect(mapStateToProps, mapDispatchToProps)

// change the export
export default Landing;

Decorators are an amazing feature to augment functionality in a declarative fashion. The code you see here works precisely the same way the other code did, it's just a bit less dense (I'd say.) The // $FlowFixMe bit is so that Flow will ignore that line: it doesn't handle decorators yet so this suppresses that warning. The reason why the transform is considered "legacy" is because the proposal is a bit in flux, but in nearly any case this code won't have to change.

We're going to revert back to using the connect(…)(…) notation for now though. Because the Flow parser doesn't support decorators (and won't until it's more stable) we can't use it since Prettier relies on the Flow parser. It works with the Babylon parser but then we can't use Flow. Make your own tradeoff there. You could put the // prettier-ignore comment to make it ignore the line too.

Okay, so we're using a form to take care of when hits enter: this is good for accessibility and a good way to take care of submitting. Once a user hits enter, it calls goToSearch where we imperatively call the router to take us to search. This will preserve our Redux state; however Search.jsx is not yet reading from Redux. Let's go fix that.

// @flow

import React from 'react';
import { connect } from 'react-redux';
import ShowCard from './ShowCard';
import Header from './Header';

const Search = (props: {
  searchTerm: string, // eslint-disable-line react/no-unused-prop-types
  shows: Array<Show>
}) => (
  <div className="search">
    <Header showSearch />
    <div>
      {props.shows
        .filter(show => `${show.title} ${show.description}`.toUpperCase().indexOf(props.searchTerm.toUpperCase()) >= 0)
        .map((show, index) => <ShowCard {...show} key={show.imdbID} id={index} />)}
    </div>
  </div>
);

const mapStateToProps = state => ({
  searchTerm: state.searchTerm
});

export default connect(mapStateToProps)(Search);

Notice we got to delete a lot of code. Always feels good! We're externalizing our state management so that'll happen more as well. Also notice that Search no longer cares about modifying searchTerm since it itself doesn't need to. This is cool; having concerns live where they happen is a really positive thing. Otherwise not much new here. This will work now if you go to Landing and submit a search term from there.

We do have to add that ESLint ignore since ESLint is not perfect. It wasn't able to track the props being used that deeply in the function. This a rare occurrence.

We broke the header. Let's go fix that.

// import at top
import { connect } from 'react-redux'
import { setSearchTerm } from './actionCreators'

// delete searchTerm and handleSearchTerm from defaultProps

// at the bottom
const mapStateToProps = state => ({ searchTerm: state.searchTerm });
const mapDispatchToProps = (dispatch: Function) => ({
  handleSearchTermChange(event) {
    dispatch(setSearchTerm(event.target.value));
  }
});

export default connect(mapStateToProps, mapDispatchToProps)(Header);

Since Header does care about modifying searchTerm we bring in that logic here. Otherwise not much changes!

Okay, so now we want to type Redux. This is no trivial feat but I promise that it will save you bugs. And in the process we'll learn more about Flow too.

Open your types.js file in the flow-typed directory and let's add some new types.

declare type ActionType = 'SET_SEARCH_TERM';

declare type ActionT<A: ActionType, P> = {|
  type: A,
  payload: P
|};

export type Action = ActionT<'SET_SEARCH_TERM', string>;

Here we're creating an enumerated typed: ActionType. This type is saying anything that's an ActionType can only be that string. Any other string (or anything else) causes an error. This will catch if you spell the action types wrong, which is nice. We'll add another action type later so you can see how to add more.

Below that, we're creating a generic action. It's saying the type (which we represent with A) will always an ActionType. It's then saying that there must be a payload type which is always going to exist. We can then define later what that type is going to be (which is what P represents.) We are not representing the full flux standard action here: it also accounts for metadata and errors, but we're keeping it simple here.

Lastly, with the Action (which is the one we export, the other two are only for use in this file) is creating the actual useable action types. We only have one: SET_SEARCH_TERM. It then defines what those that action's payloads must look like: a string. For every action we create, we'll have to come back here to define their types. This seems burdensome but it makes your Redux ironclad. The ongoing mantainability here makes it worth it, I promise.

Go back to reducers.js. We're going to refactor this a bit to make it play nicer with Flow as well as show you a cool way to write Redux: combineReducers.

// @flow

import { combineReducers } from 'redux';
import { SET_SEARCH_TERM } from './actions';

const searchTerm = (state = '', action: Action) => {
  if (action.type === SET_SEARCH_TERM) {
    return action.payload;
  }
  return state;
};


const rootReducer = combineReducers({ searchTerm });

export default rootReducer;

combineReducers creates the root reducer for you. What's peculiar of how this works as opposed to writing our own is that it separates each reducer into its own silo. Before, when writing our own, each reducer got its own copy of the entire state tree and had to be careful to not overwrite anything else it didn't intend. With combineReducers, each reducer only gets the part that it's worried about and nothing else. So, because in the combineReducers object we called the key searchTerm, the searchTerm method will only be supplied that bit of the state tree and nothing else. Thus, inside each reducer we handle its default state (for searchTerm the default value is empty string) and also have to provide for if the reducer does not recognize the action type. This is less performant but unless you're firing off a lot of actions and/or have a lot (read: dozens/hundreds) of action types, it'll make zero difference overall.

So let's roll with this and move to making async actions now.

·

redux Devtools

One of the most compelling reason to use redux is its amazing debugging experience. redux has the fantastic ability to do time-traveling debugging, meaning you can step forwards and backwards through actions you've dispatched. It's really powerful for debugging.

There are several ways to get it working but I'm going to show you the bare minimum to get up and running. Unlike React, there is some code you have to put in to get it working. Luckily, it's like two or three lines.

In Store.js put:

// @flow

import { createStore, compose } from 'redux';
import rootReducer from './reducers';

const store = createStore(
  rootReducer,
  compose(
    typeof window === 'object' && typeof window.devToolsExtension !== 'undefined' ? window.devToolsExtension() : f => f
  )
);

export default store;

That's it for code! It just adds a middleware to redux that hooks into the dev tools. It's also doing a window check to make sure if you're unit testing or running your components in node that the window reference doesn't blow up.

Now go grab the Chrome extension or Firefox extension. For Safari, Edge, and other browsers as well as React Native, there's a remote plugin way to achieve it. See here. Good news is that you can actually just build the debugger into the page so it works everywhere. Bad news is I'm going to show you how since I've never done it. In any case, feel free to explore it on your time.

Okay, last bit: this doesn't work with the file:/// protocol. But luckily we're using webpack-dev-server so it doesn't matter.

Now you should see three green circles with smaller orange circles circling the green ones on your Chrome or Firefox tool bar. Click on that and that should show you the redux tools. This allows you to play with the redux tools. I'll let you toy with them but suffice to say they're pretty impressive.

·

Async Redux

Redux by default has no mechanism for handling asynchronous actions. This means you either need handle everything async in your React (gross) or we have to augment Redux to handle async code. The former is really a non-starter: the point of Redux is centralize state manipulation and not having the ability to do async is just silly. We're building interfaces and interfaces are inherently asynchronous.

Okay, so we've decided to augment Redux. How do we do that? Well, luckily, Redux has the ability to have middlewares, just like your server can. What we can do is add a middleware that can handle more than just action objects. There are a myriad of popular ones but we're going with the most popular, the simplest, and the easiest: redux-thunk. I'll venture to say that if you build any Redux app, rarely are you not going to include redux-thunk, even if you include one of the other async Redux middlewares. It's frequently useful, even beyond it's async applications.

So let's unpack the word thunk: it's a weird computer science term that seems more difficult than it actually is. Imagine you need to write a line of code that calculates the conversion from USD to EUR. You could write it:

const dollars = 10
const conversionRate = 1.1
const euros = dollars * conversionRate

This code is a bit weak because we've statically defined the conversionRate. It would be better if we didn't have to define this value statically but instead could be determined whenever you accessed conversionRate (since currency exchange rates flucuate constantly.) What if we did:

const dollars = 10
const conversionRate = function () { return 1.1 }
const euros = dollars * conversionRate()

Now we've wrapped conversionRate in a function. Even though the answer is unchanged, conversion is now a black box that we can swap out that 1.1 whenever. The value of the return of conversionRate isn't set until that function is actually called. conversionRate is now a thunk. It's a function wrapping a value.

The above is a silly example, but it with Redux this becomes a powerfuly feature. Instead of determining what action object you're going to dispatch at write time, you can determine what you're going dispatch conditionally or asynchronously. redux-thunk even let's you dispatch multiple actions! This can be useful if you have one action that leads to multiple, cascading changes. Super useful. So let's go change the Details page to use redux-thunk instead of local state. First, let's go include the redux-thunk middleware. Go store.js:

// @flow
import { createStore, compose, applyMiddleware } from 'redux'; // add applyMiddleware
import thunk from 'redux-thunk'; // import
import rootReducer from './reducers';

const store = createStore(
  rootReducer,
  compose(
    applyMiddleware(thunk), // middleware
    typeof window === 'object' && typeof window.devToolsExtension !== 'undefined' ? window.devToolsExtension() : f => f
  )
);

export default store;

This is how you add more middlewares! Okay, so let's go add the sync action to make it so we can store omdbData in our data store. This is a good distinction to make: you still will only modify your state via reducers, and reducers are only kicked off via dispatching action synchronously to your root reducer. Always. So what we're doing is kicking off an async action which when it finishes will dispatch a sync action to the root reducer. We're just adding another step. So let's do our sync action. Go to actions.js:

export const ADD_API_DATA = 'ADD_API_DATA';

Go to types.js

// new action type
declare type ActionType = 'SET_SEARCH_TERM' | 'ADD_API_DATA';

// new action
export type Action = ActionT<'SET_SEARCH_TERM', string> | ActionT<'ADD_API_DATA', Show>;

We're expanding how many different types of actions we can have.

Now go to reducers.js:

// at top
import { SET_SEARCH_TERM, ADD_API_DATA } from './actions';

const DEFAULT_STATE = {
  searchTerm: '',
  apiData: {}
};

// add new reducer
const apiData = (state = {}, action: Action) => {
  if (action.type === ADD_API_DATA) {
    return Object.assign({}, state, { [action.payload.imdbID]: action.payload });
  }
  return state;
};

// add new reducer
const rootReducer = combineReducers({ searchTerm, apiData });

Doing some deep merging, but really nothing new here.

You can can totally do this with the other style of reducers we were doing but as you can see you get really clean code this way. We should also talk about type refinement at this point and why we went with the combineReducers style of writing Redux. There, where we check to see if action.type is ADD_API_DATA is called a type refinement in the eyes of Flow. As soon as we enter the body of that if statement, we are positive that the payload of that action is a Show and we're free to access all the data that a Show should have. We're positive because that's what our types dictate that to us. If we try to access action.payload.imdbID outside of that if statement, you'll get a Flow error because we are not yet certain of its type. It's a bit burdensome to get to this point but that assurance of the types is going to save you a bunch of run time errors.

Go to actionCreators.js:

// @flow

import axios from 'axios';
import { SET_SEARCH_TERM, ADD_API_DATA } from './actions';

export function setSearchTerm(searchTerm: string) {
  return { type: SET_SEARCH_TERM, payload: searchTerm };
}

export function addAPIData(apiData: Show) {
  return { type: ADD_API_DATA, payload: apiData };
}

export function getAPIDetails(imdbID: string) {
  return (dispatch: Function) => {
    axios
      .get(`http://localhost:3000/${imdbID}`)
      .then(response => {
        dispatch(addAPIData(response.data));
      })
      .catch(error => {
        console.error('axios error', error); // eslint-disable-line no-console
      });
  };
}

First we add an action creator for our sync action, addAPIData. This is personal preference but I always make action creators that dispatch object a separate function. I could have done this directly inside of getOMDBDetails (and many people do) but I like keeping it separate for code organization and reuseability.

So let's unpack getAPIDetails. First this is to notice that it's a function that returns a function, a thunk. Redux will call this returned function (you call the outer one and pass that to dipatch) and pass into this returned function a dispatch function and a getState function. We don't need getState, but if your actionCreator needs to refer to the state of the Redux store, you'd call this function.

Inside of the returned function, we make our AJAX call and then dispatch an action via the addAPIData action creator with the new data. That's it! This is a pretty simple (and common) application of thunk, but you can get much more robust with it. Since you have a dispatch function, you're free to dispatch multiple actions, or not dispatch any at all, or conditionally dispatch one/many/none. Okay, so now head to Details.jsx to integrate it.

// new imports
import { connect } from 'react-redux';
import { getOMDBDetails } from './actionCreators';

// delete axios import

// more propTypes (outside of show)
props: {
  rating: string,
  getAPIData: Function,
  show: Show
};

// replace componentDidMount
componentDidMount() {
  if (!this.props.rating) {
    this.props.getAPIData();
  }
}

// change state to props in render
if (this.props.rating) {
      rating = <h3>{this.props.rating}</h3>

// replace export at bottom
const mapStateToProps = (state, ownProps) => {
  const apiData = state.apiData[ownProps.show.imdbID] ? state.apiData[ownProps.show.imdbID] : {};
  return {
    rating: apiData.rating
  };
};

const mapDispatchToProps = (dispatch: Function, ownProps) => ({
  getAPIData() {
    dispatch(getAPIDetails(ownProps.show.imdbID));
  }
});

export default connect(mapStateToProps, mapDispatchToProps)(Details);

Okay, so now we have an interesting side effect of moving from React to Redux: when we were in React whenever we navigated away from a Details page, we lost the data we requested from the API and we'd have to re-request it anew every time we navigated to the page. Now since we've centralized to Redux, this data will survive page transitions. This means we need to be smart and only request data when we actually don't have it, hence the conditional in the componentDidMount method. If we already have data, no need to dispatch the action!

In the mapStateToProps, we're including the ownProps parameter. This is the props being passed down from its parent component which we need to select the correct show to pass in as props. It also has the benefit of making connect subscribe to props change so that mapStateToProps will change as the props change. If we suddenly switched the imdbID being passed in, this would still work just fine.

That's it! That's async Redux, or at least the simplest form of it. Like I alluded to earlier, there are several other ways of accomplishing async Redux. The other popular options include redux-promise where you dispatch promises, redux-observable where you dispatch observables, and redux-sagas where dispatch generators.

·

Fixing Our Tests and Testing Redux

So we broke all of our tests. They all fail now. High five! This is a big reason why I'm hesitant to test UI code: I find my tests break all the time just because I'm rewriting markup or other code. Nonetheless, let's refix our tests and add two for Redux. As opposed to testing React which I don't do much of, I test the hell out of my Redux code. Redux code is very testable by design and you should cover all or nearly-all of your reducers with tests.

At the end of Search.jsx add:

export const Unwrapped = Search

We need an "unconnected" version of Search to test. We're decoupling this from Redux so we can just test the React portion. It will still work without Redux as long as we pass in proper parameters. Go to Search.spec.jsx.

// import the new Unwrapped Search as just Search
import Search, { Unwrapped as UnwrappedSearch } from '../Search';

// in the first test, change the shallow call
const component = shallow(<UnwrappedSearch shows={preload.shows} searchTerm='' />);

// in the second test, change the shallow call
const component = shallow(<UnwrappedSearch shows={preload.shows} searchTerm='' />);

// go ahead and comment out the last test so we can test these first two first

Once we provide the proper params, these tests will be able to pass again. The snapshot is going to fail because we wrapped Header with a connect but go ahead and run npm run update-test to take care of that.

Since the last test tests the integration of Header and Search which were previously married together, we're going to need to do two things: switch our render to be able to render Header inside of Search instead of just stubbing it out and we're going to have to bring in Redux and integrate that.

// imports at top
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import store from '../store';
import { setSearchTerm } from '../actionCreators';
import { shallow, render } from 'enzyme' // add render import

// replace last test
test('Search should render correct amount of shows based on search', () => {
  const searchWord = 'New York';
  store.dispatch(setSearchTerm(searchWord));
  const component = render(
    <Provider store={store}>
      <MemoryRouter>
        <Search shows={preload.shows} />
      </MemoryRouter>
    </Provider>
  );
  const showCount = preload.shows.filter(show =>
    `${show.title.toUpperCase()} ${show.description.toUpperCase()}`.includes(searchWord.toUpperCase())
  ).length;
  expect(showCount).toEqual(component.find('.show-card').length);
});

We need to simulate events to Redux instead of to the DOM. Ultimately this isn't a big deal since you should be testing that action creator individually anyway. We also need to use Provider to make Redux work for Header since that's how Header and Search communicate now. Also, we can't do the ShowCard component trick anymore with render since it's not stubbing out ShowCard so we're just checking for the CSS class instead.

Since our ShowCard is using styled-components, we don't know what that CSS class is actually going to be. Luckily, we can add our own class on there. Go to ShowCard.jsx and add:

// replace wrapper
<Wrapper className="show-card" to={`/details/${this.props.imdbID}`}>
  […]
</Wrapper>

Notice we also have to bring in react-router's MemoryRouter too. Since ShowCard has a Link in it, it depnds on a router being available. Since we're not in the DOM and not testing router-related functionality, it's enough to use the MemoryRouter which will allow it to work without having a DOM present. Super useful.

There's a layer deeper that you can go with Enzyme: static rendering with Cheerio. If you need to do serious manipulation, this is the tool you need to go with. Be forewarned this slows down startup a lot since it brings in jsdom and it is slow as 💩 to start up.

Cool! Let's go test Redux now.

One of the primary reasons to use Redux is how testable it is. It was a big part of its design. Redux makes you create pure functions. These functions are then able to pulled out and thoroughly tested. And, lucky for us, Redux dev tools now lets you generate test automatically! Open Search and paste the word "orange" in there. We paste it so it's one atomic operation. Open the Redux dev tools and select the last action. Click on the test tab. You'll see an automatically generated test! Copy that and paste it into reducers.spec.js. You may have to mess with the paths to get it correct. You also may have to modify the let to be a const to get ESLint to shut up. I also like to name the tests after their action name.

import reducers from '../reducers';

test('SET_SEARCH_TERM', () => {
  const state = reducers({ searchTerm: '', apiData: {} }, { type: 'SET_SEARCH_TERM', payload: 'orange' });
  expect(state).toEqual({ searchTerm: 'orange', apiData: {} });
});

Free tests. All we have to do is recreate what we want to test, copy, paste, and commit! Pretty slick. In this particular case, it's not terribly interesting. But you can't beat how low effort it is to get a test out the door. And if you have to later throw this test away you won't care because it was a minute to create start-to-finish. And if you get more complex Redux actions you can save a lot of time doing this. Let's also grab the @@INIT action to make sure we bootstrap the way we think.

test('@@INIT', () => {
  const state = reducers(undefined, {});
  expect(state).toEqual({ searchTerm: '', apiData: {} });
});

Add one more for ADD_API_DATA after navigating to Details page:

test('ADD_API_DATA', () => {
  const state = reducers(
    { searchTerm: 'orange', apiData: {} },
    {
      type: 'ADD_API_DATA',
      payload: {
        rating: '0.8',
        title: 'Orange Is the New Black',
        year: '2013–',
        description: 'The story of Piper Chapman, a woman in her thirties who is sentenced to fifteen months in prison after being convicted of a decade-old crime of transporting money for her drug-dealing girlfriend.',
        poster: 'oitnb.jpg',
        imdbID: 'tt2372162',
        trailer: 'th8WT_pxGqg'
      }
    }
  );
  expect(state).toEqual({
    searchTerm: 'orange',
    apiData: {
      tt2372162: {
        rating: '0.8',
        title: 'Orange Is the New Black',
        year: '2013–',
        description: 'The story of Piper Chapman, a woman in her thirties who is sentenced to fifteen months in prison after being convicted of a decade-old crime of transporting money for her drug-dealing girlfriend.',
        poster: 'oitnb.jpg',
        imdbID: 'tt2372162',
        trailer: 'th8WT_pxGqg'
      }
    }
  });
});

Let's go test our actionCreators. Create a new spec called actionCreators.spec.js and add this:

// @flow

import { setSearchTerm, addAPIData } from '../actionCreators';

test('setSearchTerm', () => {
  expect(setSearchTerm('New York')).toMatchSnapshot();
});

test('addAPIData', () => {
  expect(
    addAPIData({
      rating: '0.8',
      title: 'Orange Is the New Black',
      year: '2013–',
      description: 'The story of Piper Chapman, a woman in her thirties who is sentenced to fifteen months in prison after being convicted of a decade-old crime of transporting money for her drug-dealing girlfriend.',
      poster: 'oitnb.jpg',
      imdbID: 'tt2372162',
      trailer: 'th8WT_pxGqg'
    })
  ).toMatchSnapshot();
});

Since actions are just objects that easily serializable, snapshots are perfect to test them. This way if their shape ever changes, you'll instantly know (if it somehow slips by Flow) and it'll be easy to update if that's anticipated.

Okay, now let's test our thunk. This is a bit trickier to test since we need to handle asynchronous behavior and mock out AJAX requests. Luckily [moxios][moxios] is a nice helper for axios for testing. Let's take a look at what that looks like:

// at the top
import moxios from 'moxios';

// move this to an object that we can reuse
const oitnb = {
  rating: '0.8',
  title: 'Orange Is the New Black',
  year: '2013–',
  description: 'The story of Piper Chapman, a woman in her thirties who is sentenced to fifteen months in prison after being convicted of a decade-old crime of transporting money for her drug-dealing girlfriend.',
  poster: 'oitnb.jpg',
  imdbID: 'tt2372162',
  trailer: 'th8WT_pxGqg'
};

// modify the addAPIData test to use the object
test('addAPIData', () => {
  expect(addAPIData(oitnb)).toMatchSnapshot();
});

// at the bottom
test('getAPIDetails', (done: Function) => {
  const dispatchMock = jest.fn();
  moxios.withMock(() => {
    getAPIDetails(oitnb.imdbID)(dispatchMock);
    moxios.wait(() => {
      const request = moxios.requests.mostRecent();
      request
        .respondWith({
          status: 200,
          response: oitnb
        })
        .then(() => {
          expect(request.url).toEqual(`http://localhost:3000/${oitnb.imdbID}`);
          expect(dispatchMock).toBeCalledWith(addAPIData(oitnb));
          done();
        });
    });
  });
});

Notice we're providing a done function to the test. This is because this is an async test and without it the test will complete synchronously. We need it to wait until the async behavior completes.

The first thing in the function we're doing is creating a spy function with Jest. This is the same as what [sinon][sinon] does for you: it's a function we can use to make sure that the callbacks are being called with the write parameters and the correct amount of times.

Next we're calling moxios.withMock. This is specific to moxios and something you actually could achieve with Jest's mocking capabilites alone but since moxios exists it's an easy companion to use with axios. This will stub out the axios require inside of actionCreators.js and make it so it's mocked instead of actually trying to make an AJAX call.

Inside we invoke the actionCreators, first creating the thunk with the correct ID, then invoking the returned thunk with the mocked dispatch function. A bit weird, but it makes sense. The actionCreators returns a function (which typically Redux calls for you with its dispatch function) and we call it with our mock dispatch function.

After we invoke the thunk, we have to wait since it's async code, hence the wait function. Once inside there, we can inspect the moxios request, tell it what to respond with, and then examine that response after the promise it returns completes. This can be confusing due to the multiple levels of async we're dealing with to achieve the ability to test the dispatch params and the URL axios is calling, but I'd say with it. Now we can be certain the API request is happening to the URL we anticipate and it's creating appropriate action based on the API call made.

That's how you create a test for a thunk! As you may see, when you introduce Redux to a React codebase, it makes the data layers a bit easier to test (testing that async behavior in React would be tough; it's not too bad in Redux) but at the cost of making React much tougher to test due to the Redux integration. Again, make your own judgment call as to what's important to you.

·

Universal Rendering

Universal rendering, or the artist formerly known as isomorphic rendering. The idea here is that you server-side prerender your code so that when it gets down to the client, your browser can instantly show the markup while your app bootstraps in the background. It makes everything feel very instantaneous.

With just vanilla React, universal rendering is a cinch. Check out the whole node file from another one of my workshops. It does server-side rendering in just a few lines.

It's not quite so simple now that we have routing involved. We don't want to have to duplicate all of our routing info that we wrote for react-router. Rather, if possible, we just want to reuse the routes we already built for react-router. So let's do that (with some refactoring.)

First thing you typically need to do when getting ready to implements server-side rendering (SSR) is split browser concerns and app concerns. The key is anything in the initial render path cannot reference anything in the DOM or window. We can't make AJAX calls, reference the window, or anything else browser specific. All the browser specific code has to live in ClientApp.jsx (which won't get included in Node,) componentDidMount (which doesn't get called,) or behind some sort of if (window) conditional.

We're pretty close to good as is. The only thing we need to do is move BrowserRouter from App to ClientApp. In Node we'll use a ServerRouter so we need the Browser one to only to get included client-side. So remove BrowserRouter from App altogether and wrap the <App /> in the render call in ClientApp with <BrowserRouter></BrowserRouter> (after importing it.)

That should be enough. ClientApp should look like:

// @flow

import React from 'react';
import { render } from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';

const renderApp = () => {
  render(
    <BrowserRouter>
      <App />
    </BrowserRouter>,
    document.getElementById('app')
  );
};
renderApp();

if (module.hot) {
  module.hot.accept('./App', () => {
    renderApp();
  });
}

You may still have the Perf stuff in there. At this point you should take it out.

Now all browser concerns lie in ClientApp and the general app has been split out and is ready to be server renderered. We'll use a special ServerRouter for server rendering so that's why we put the BrowserRouter inside of ClientApp.

Okay, now we need to go make it so that index.html can be used as a template. There Number.POSITIVE_INFINITY ways of doing this, I'm just going to show you one (hopefully easy) way of doing it: with Lodash. First go add the <%= body %> template tag to index.html inside of #app like so:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>svideo</title>
  <link rel="stylesheet" href="/public/style.css" />
</head>
<body>
  <div id="app"><%= body %></div>
  <&NegativeMediumSpace;script src="/public/bundle.js"></script>
</body>
</html>

This is Lodash-specific templating. We'll use it as we server-side render.

Go to .babelrc and add env, for server. For now it'll be the same as test (since we need Babel to make the modules to CommonJS here too) but we don't want to tie those together.

{
  "presets": [
    "react",
    ["env", {
      "targets": {
        "browsers": "last 2 versions"
      },
      "loose": true,
      "modules": false
    }]
  ],
  "plugins": [
    "react-hot-loader/babel",
    "babel-plugin-transform-decorators-legacy",
    "babel-plugin-transform-class-properties"
  ],
  "env": {
    "server": {
      "plugins": ["transform-es2015-modules-commonjs"]
    },
    "test": {
      "plugins": ["transform-es2015-modules-commonjs"]
    }
  }
}

Okay, let's create a server now! Create a server.js outside the js folder and put it just in the root directory of your project. Put:

/* eslint no-console:0 */
require('babel-register');

const express = require('express');
const React = require('react');
const ReactDOMServer = require('react-dom/server');
const ReactRouter = require('react-router-dom');
const _ = require('lodash');
const fs = require('fs');
const App = require('./js/App').default;

const StaticRouter = ReactRouter.StaticRouter;
const port = 8080;
const baseTemplate = fs.readFileSync('./index.html');
const template = _.template(baseTemplate);

const server = express();

server.use('/public', express.static('./public'));

server.use((req, res) => {
  const context = {};
  const body = ReactDOMServer.renderToString(
    React.createElement(StaticRouter, { location: req.url, context }, React.createElement(App))
  );

  if (context.url) {
    res.redirect(context.url);
  }

  res.write(template({ body }));
  res.end();
});

console.log(`listening on ${port}`);
server.listen(port);

We're switching back to CommonJS here to work with Node; Node doesn't natively understand ES6 modules so we need to use CommonJS. We require in a bunch of stuff. We're using Lodash templates but that's a detail; I just did it since it's an easy way to template. There's ten billion other ways to do it. We do some static serving for our CSS and bundled JS. And then we do the magic of server side rendering.

The context object we're feeding into the StaticRouter is to handle the 404 and redirect cases.

babel-register at the top lets us require modules that need transpilation.

Okay. Let's run the app. Run in your CLI npm run build (to build your bundle) then run NODE_ENV=server node server.js. Make sure you re-run build because the webpack-dev-server doesn't necessarily re-write out the bundle.js. Okay, so now try going to localhost:5050. While you won't necessarily notice it loading quicker since you were developing locally, check out view source. You should see it ships with a bunch of markup which means your page will load much quicker on a slower connection since markup will start rendering before the JS is done downloading.

Congrats! You've done server-side rendering! Now, we messed up hot module reload. It'd be great if we didn't have to choose between SSR and HMR. And we don't! Let's go include that too. First go to your webpack config and let's change just one thing:

// replace the entry:
entry: ['webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000', './js/ClientApp.jsx'],

We need webpack to look for the webpack middleware instead of the dev server. After doing this, the dev server will not work and you can only use the server version. So let's go make the server work as well.

// more includes
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const webpack = require('webpack');
const config = require('./webpack.config');


// after the creation of server, before server.use('public' …)
const compiler = webpack(config);
server.use(
  webpackDevMiddleware(compiler, {
    publicPath: config.output.publicPath
  })
);
server.use(webpackHotMiddleware(compiler));

Now you should be able to run NODE_ENV=server node server.js (or however you set environment variables in your shell, this works for bash) and get SSR and HMR! Let's go modify our dev command in package.json to use our server instead of webpack-dev-server.

"dev": "NODE_ENV=server nodemon server.js",

Nodemon is a dev helper tool that will automatically restart the server that we make changes to server.js. Shouldn't need it now but it's useful once you want to start changing server.js. So now try yarn dev and see if it works (make sure your webpack-dev-server is not running.) You should see everything working as expected.

·

Webpack Code-Splitting and Async Routing

So far all of our routing with react-router has synchronous which makes sense. When we detect that a user has requested a route, we already have that route in our bundle.js and we render and serve that to them. The logic follows.

However, as our app grows and grows, our bundle.js is going to get bigger and bigger in file size. Wouldn't it be better if you were on Search that it served you just the JavaScript you need for that page and none of the JS for Landing or Details? For example, Search doesn't need the axios client we brought in: that client can safely just be loaded on the Details page.

Enter webpack's code splitting ability. It's smart enough to know which files are required by which other files and thus if you choose to use webpack's async loading API (import(…)) then webpack will automatically start chunking your JS for you. What's more is we don't have to write the glue code that will download the chunks as we need them: webpack just does this for us. All we have to do is identify the modules that can be async by treating them as if they were. Really cool.

So we're going to treat all of our routes as async and luckily react-router is already instrumented for this for both server and client-side. So let's go make it happen! We're going to be using to do this. There are many ways to do this; I've just found this easiest to teach you. People right now like react-modules despite it having some issues with server-side rendering.

Let's go create a component that will handle our asynchronous routes to contain all that craziness. Create a file called AsyncRoute and go there.

// @flow

import React from 'react';
import Spinner from './Spinner';

class AsyncRoute extends React.Component {
  state = {
    loaded: false
  };
  componentDidMount() {
    this.props.loadingPromise.then(module => {
      this.component = module.default;
      this.setState({ loaded: true });
    });
  }
  component = null;
  props: {
    props: mixed,
    loadingPromise: Promise<{ default: Class<React.Component<*, *, *>> }>
  };
  render() {
    if (this.state.loaded) {
      return <this.component {...this.props.props} />;
    }
    return <Spinner />;
  }
}

export default AsyncRoute;

AsyncRoute is going to passed a promise which will resolve to a module. Once that promise has completed, that means the module is loaded and available. Then we can render it. Notice that we stick the module on this and not into state. Modules are large and it would slow down our component to have so much state. Furthermore we don't expect it to change. Before that we'll render a loading state. That's all we're going to do with AsyncRoute.

Now we need to enable Babel, Webpack, and Node to all understand the import(…) syntax. This is brand new and only stage 3. Thus we need to include a few more plugins. We need one just so Babel can understand import at all, and one to transform so Webpack will know to split there. Add the following to the top level plugins array:

"plugins": [
  "react-hot-loader/babel",
  "babel-plugin-syntax-dynamic-import",
  "babel-plugin-dynamic-import-webpack",
  "babel-plugin-transform-decorators-legacy",
  "babel-plugin-transform-class-properties"
],

Go to App.jsx

// replace Landing Match
<Route exact path="/" component={props => <AsyncRoute props={props} loadingPromise={import('./Landing')} />} />

So now we're using our AsyncRoute function to make Landing Async. First we import our route. Then we pull in our AsyncRoute and use it inside of Route. This is amazing since Webpack knows to perform a code split here and we get all the rest of that for free.

Let's talk about what sucks about this. Now, server-side rendered or not, we get a loading screen first thing. No matter what. Ideally we get this loading screen sooner but nonetheless that happens. There are ways around this but it involves either making some compromises by not server-side rendering properly and getting a checksum violation or by greatly increasing the complexity of this by introducing the concept of module hydration where on the server you make sure to send down the bundle and the correct chunk at the same time and detect that on the client. For now I'm happy just introducing code-splitting to our app for now.

Also, in order for import() (or require.ensure, which is the CommonJS version) to be able to code split, the parameter passed to it must be a string of the path. It cannot be a variable. Webpack is doing static analsysis of your code and cannot follow variables.

Open up your browser to /search (without hitting / first) and watch the network tab. Make sure your npm run watch and your npm run start are both running. You should see bundle.js being downloaded but you should also see 0.bundle.js being downloaded too. This is the chunks that Webpack is sending down piecemeal, meaning your route and associated modules are not included in the initial payload. This becomes a bigger and bigger deal as your app expands. Let's finish the rest of our async routes.

// delete Search and Details import

// replace Details and Search matches
<Route
  path="/search"
  component={props => (
    <AsyncRoute loadingPromise={import('./Search')} props={Object.assign({ shows: preload.shows }, props)} />
  )}
/>
<Route
  path="/details/:id"
  component={(props: { match: Match }) => {
    const selectedShow = preload.shows.find((show: Show) => props.match.params.id === show.imdbID);
    return (
      <AsyncRoute
        loadingPromise={import('./Details')}
        props={Object.assign({ show: selectedShow, match: {} }, props)}
      />
    );
  }}
/>

Nothing too crazy here either. Just extendingo out the same ideas. Now try navigating around your app and watch the network tab. You should different bundles being pulled in. If you look at your terminal output, you'll see we actually haven't optimized too much: our main bundle is nearly a megabyte and the smaller bundles are between three and fifty kilobytes. Like I said, this is wonderful for big apps where you can section off where dependencies. For example the fifty kilobyte bundle is the only one that has axios. The rest of the app doesn't need it. But for our tiny React routes, this isn't super useful. And the ability to codesplit isn't free either: Webpack includes some glue code to make this work. So evaluate this tool carefully!

Our problem now is that we've broken hot module reload. Unfortunately, with Webpack in the state it's a choose-two situation with code-spliting, hot module replacement, and server side rendering. You can set up two different webpack configs since you only need code splitting on the front end and you only need HMR in dev: I leave that to you.

Lastly, let's set up our build for production. Go modify build in package.json's scripts to be:

"build": "webpack -p",
"build:dev": "webpack -d",

-p optimizes Webpack for production with Uglify and builds React in production mode. -d builds in debug mode and includes much more verbose logging. The sizes you see for -p are minified and uglified (including tree shaking) but are not gzipped. Usually your server does that automatically.

Go modify your Webpack config's devtool line to be

devtool: process.env.NODE_ENV === 'development' ? 'cheap-eval-source-map' : false,

Source maps are huge and this will only ship them in dev. You'll also need to conditionally include the webpack middleware stuff: refactor to look like this:

const path = require('path');
const webpack = require('webpack');

const config = {
  context: __dirname,
  entry: ['./js/ClientApp.jsx'],
  devtool: process.env.NODE_ENV === 'development' ? 'cheap-eval-source-map' : false,
  output: {
    path: path.resolve(__dirname, 'public'),
    filename: 'bundle.js',
    publicPath: '/public/'
  },
  devServer: {
    hot: true,
    publicPath: '/public/',
    historyApiFallback: true
  },
  resolve: {
    extensions: ['.js', '.jsx', '.json']
  },
  stats: {
    colors: true,
    reasons: true,
    chunks: false
  },
  plugins: [new webpack.HotModuleReplacementPlugin(), new webpack.NamedModulesPlugin()],
  module: {
    rules: [
      {
        enforce: 'pre',
        test: /\.jsx?$/,
        loader: 'eslint-loader',
        exclude: /node_modules/
      },
      {
        test: /\.jsx?$/,
        loader: 'babel-loader'
      }
    ]
  }
};

if (process.env.NODE_ENV === 'development') {
  config.entry.unshift('webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000');
}

module.exports = config;

Add the development env to your .babelrc

"development": {
  "plugins": ["transform-es2015-modules-commonjs"]
},

And now we can only run the dev middleware in dev, as well as gzip our output. This is often done at the reverse proxy layer (like Nginx) but let's do it here for fun. Refactor your server to be:

//include
const compression = require('compression');

server.use(compression());
if (process.env.NODE_ENV === 'development') {
  const compiler = webpack(config);
  server.use(
    webpackDevMiddleware(compiler, {
      publicPath: config.output.publicPath
    })
  );
  server.use(webpackHotMiddleware(compiler));
}

This should get our first chunk down to about 105KB. Not bad. Let's see if we can press even harder with Preact.

·

Preact

So this is a React work, why the 💩 are we talking about Preact? Let me tell you why! Preact is amazing! Preact is an almost drop in replacement for React that is much smaller in file size while maintaing nearly all the features and actually being faster (or so my limited, flawed benchmarking leads me to believe.) One thing is for sure though, it's much smaller!

It sounds just better, right? Well, it's certainly something you and your company should discuss. Preact is just 3KB gzipped; that's really small! React for comparison's sake is around 45KB, give or take 5KB. That's a huge difference! The way Preact is able to achieve a lot of the size difference is by cutting out some of the legacy bits of React, letting the browser do more heavy lifting, and focusing on a smaller API.

Speaking of a smaller API, we can't switch to Preact wholesale as-is right now; we're using the React and ReactDOM package everywhere.

So instead we're going to drop in the preact-compat library which backfills those APIs at the cost of adding 5KB to your payload; still a win. preact-compat gets you started on your migration; eventually you want to drop it and just be on Preact.

So we're going to super quick migrate to Preact for the client side. In webpack.config.js, add:

// inside resolve
alias: {
  react: 'preact-compat',
  'react-dom': 'preact-compat'
},

// inside 'babel-loader' loader
include: [
  path.resolve('js'),
  path.resolve('node_modules/preact-compat/src')
]

The include parts is just telling babel-loader to only run on files that are in the js directory (our code) or in the preact-compat directory. We should have done this sooner since this will speed up your build by not running on every file in node_modules.

The alias bit is telling webpack that everytime in our app we ask for react or react-dom, to actually give it preact-compat (which itself encompasses Preact.) Now build your app for production and compare! In my local env, I'm seeing a difference of 51KB vs 105KB for production builds, gzipped. Not bad!!

Our server more-or-less works as is. It's not ideal; honestly you'd want Preact doing both server and client work, but this is good for now. If you want to make it work, you'll need to make Babel alias React to Preact instead of Webpack since that runs both client and server side whereas Webpack doesn't.

You can also do this for the Inferno library as well. Similarly small and blazing fast, both Preact and Inferno are amazing.

·