Skip to content

davefancher/ReactReduxWorkshop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

65 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

React/Redux Workshop Outline

Welcome!

  • Housekeeping
  • Facilities
  • Introductions

Prerequisite Checks


Module 1: Introducing React

  • What is React?

On a basic level, it's a library for building user interfaces with JavaScript. It began at Facebook in 2011 and was open sourced in 2013. Since then, it's grown to be used and supported by many major companies and it now has robust 3rd party library integrations and the most framework specific libraries. Beyond the web, it's become a paradigm of sorts for building mobile, desktop, tv, and other kinds of applications with JavaScript.

The idea is that instead of using the standard layout of separating concerns into JavaScript, HTML, and CSS. You will render your views (and potentially css as well) from your JavaScript.

On September 22, 2017 Facebook announced that they were changing the license used by React. As of version 16, React will no longer use the BSD + Patents license and will instead use the MIT license.

  • Anatomy of a React application
    • Components
    • Props - read-only; the static pieces
    • State - updatable; the pieces that change

A React app is made up of a series of components which can contain their own dynamic state. Here is an example of a simple counter component which maintains it's own state: React Component Screenshot

And ends up looking like this when rendered (more on rendering in a future section): counter view

If state or some kind of data is passed to another component, it is called props and is read only. If we take the previous example and move the buttons into a child component, we can pass any functions or data we need to the child using props:

// ButtonHolder.js
import React from 'react';

const ButtonHolder = (props) => {
  var {addOne, sub} = props;

  return (
    <div>
      <button className="button" onClick={addOne}>+</button>
      <button className="button hollow" onClick={sub}>-</button>
    </div>
  )
}

export default ButtonHolder;
// App.js
import React, { Component } from 'react';
import ButtonHolder from './ButtonHolder';

class App extends Component {
  constructor () {
    super();
    this.state = {
      counter: 0
    };
  }
  add () {
    this.setState({
      counter: this.state.counter + 1
    })
  }
  subtract () {
    this.setState({
      counter: this.state.counter - 1
    })
  }
  render() {
    return (
      <div className='content-component'>
        <h1>Counter {this.state.counter}</h1>
        <ButtonHolder addOne={this.add.bind(this)} sub={this.subtract.bind(this)}/>
      </div>
    );
  }
}
export default App;
  • Note: In React applications, data always flows downward. Displaying and manipulating props will never change the state in the parent component. When state changes in the parent, it will effect children relying on that state, but not the other way around.

Module 2: My First React Project (Overview & Setup)

Throughout the workshop we'll gradually build out an application that allows us to interact with some data from An API of Ice and Fire. The application will be a great foundation for continuing study as it will be loaded with examples of how to accomplish a variety of tasks with React and Redux.

Demo: The finished app

The initial setup for a React application can be a bit tedious but it's important to see how the various packages interact to make our application work. There are some tools such as create-react-app to automate some of this but since it's only necessary once per project we'll take the time to walk through the process today. For more information, see Appendix C.

Initialize a repository

Optional but recommended

git init

Copy .gitignore from the Module folder.

Initializing the npm package:

npm init

Accept the defaults. You should now have a basic package.json file

Installing React

Now we'll install the core React components: react and react-dom

npm install react react-dom --save

Note that install can be shortened to i in each of the npm commands.

These packages provide all of React's core functionality and the entry point for rendering React components.

Installing Babel

Although it's possible to write a React application with "vanilla" JavaScript it's far more convenient (and productive!) to use React's JSX extension which allows us to specify components with a markdown-like syntax. In order to achieve this we'll need a transpiler that knows how to convert JSX to JavaScript. That's where Babel comes in.

npm install babel-core babel-loader babel-preset-env babel-preset-react --save

Here we've installed several packages that include the core Babel functionality along with some extensions to handle ES6 and JSX.

Installing Webpack and the Dev Server

As our application grows in complexity we'll quickly find ourselves wanting to better organize our code but that brings along another set of challenges such as how to package and deploy all that code. Webpack solves that problem by providing a convenient bundling system. Let's add that to our project now.

npm install webpack webpack-dev-server react-hot-loader@3.0.0-beta.7 --save

While we're still installing packages, let's also take care of adding some additional packages for compiling SASS sheets as well. We won't be doing much custom styling today but we might as well include them.

npm install node-sass sass-loader css-loader style-loader --save

Configuring Babel and Webpack

Now that Babel and Webpack are installed we need to configure them. Copy both .babelrc and webpack.config.js from the Module2 folder.

Babel uses the .babelrc file to identify which transformers to use to convert our modern JSX code to JavaScript.

Similarly, Webpack uses the webpack.config.js file to identify not only what to build but also how to build it and to host it with the webpack dev server.

We could also have used the Express server here but the Webpack dev server works really well with Webpack and supports easy hot reloading of modules.

Creating the Basic Application Structure

If you look at the webpack.config.js file you'll see a few specific paths referred to throughout the file. These paths describe the compilation entry point as well as the output destination. Let's create the structure that Webpack is expecting to find. (Note that Webpack will automatically create the destination files and folders.)

We'll start with the dev folder which will contain our JavaScript and SASS code.

mkdir dev

Next, let's add an index.html file in our application root. You can copy the file from the Module2 folder.

Finally, let's add the index.jsx and site.scss files to the dev folder. You can again copy these from the Module2 folder.

Configuring Build and Start Scripts

The last step before we can start building our React application is to configure some build and start scripts that tell node what to do when npm build or npm start is executed. To do so, open the package.json file and add the following two lines to the scripts node:

"build": "webpack --config ./webpack.config.js --progress --colors",
"start": "webpack-dev-server --open"

Building the Project

All of the required packages and configuration should now be in place so we can finally compile some code! Go ahead and run:

npm start

This will compile the code and start the webpack-dev-server. It will also open your default browser and navigate to the new page.

Congratulations! Your app is now configured!


Review Module 2:

  1. Explain the toolchain used for bundling, building, and transpiling React applications.
  2. What are some of the features and reasons to use webpack-dev-server?

Module 3: My First React Project: Hello World

In the last section we built our workspace and imported all the necessary components to build a simple React application but we haven't actually started using React yet since all we're serving up is some HTML.

In this section we'll begin gradually building out the application we previewed at the beginning of the last section.

Defining a Component, the Functional Way

In keeping with software development tradition let's begin our introduction to React with a simple "Hello World" component. For convenience we'll define this first component directly within the /dev/index.jsx file.

function HelloWorld () {
    return (
        <div>Hello world!</div>
    );
}

The above snippet defines a very simple functional component called HelloWorld. As you probably guessed, functional components are so named because they're defined as JavaScript functions. Let's take a moment to examine the code before we begin using it.

Of particular importance here is how this appears to be a blend of JavaScript and HTML. This is JSX - a declarative extension to JavaScript that lets us write logic in JavaScript while expressing the resulting element(s) in their natural form.

It's important to note, however, that the HelloWorld function does not return HTML but instead returns a specialized React object; the JSX syntax is syntactic sugar over much more verbose JavaScript function calls.

Since you'll almost never use the long-form we won't cover it here.

Another important item of note is that all React components must return one of the following:

  • A single root element (such as div)
  • A single value
  • null

This restriction exists because again, we're just building JavaScript objects. (*Note: React Fiber will allow you to return multiple root elements. See Appendix B: Resources.)

Now that we have our component and understand a bit of what's going on, let's see how to start using it within our application.

Using the HelloWorld Component

To use the HelloWorld component within the application we need to tell our page about it. Because we're working within the index.jsx file for the time being, all we need to do is tell React to put it on the page. This is where the ReactDOM.render function comes into play.

Below the HelloWorld definition enter the following:

ReactDOM.render(
    <HelloWorld />,
    document.querySelector("#container")
);

ReactDOM.render accepts two arguments:

  1. The component to render
  2. A reference to the element where the component will be rendered. By using document.querySelector we're able to select an element in our HTML document by CSS selector. The way we're using document.querySelector here is functionally equivalent to using document.getElementById.

Now save the file and observe the change within your browser window. Provided that the react-hot-loader package is installed and configured correctly within webpack you should see this change reflected automatically.

Notice how we represented the HelloWorld as a JSX element rather than invoking the function. Babel knows to look for a function that matches the element name. It also knows how to pass additional information to that function so it can be used within the component.

Speaking of which...

Hello Props

props are one of the mechanisms we can use to pass data down to a component. They typically represent specific display elements or other things that affect formatting.

Let's add some props to change the text that the HelloWorld component renders.

First we'll need to modify the HelloWorld function as follows:

function HelloWorld (props) {
    return (
        <div>Hello {props.firstName} {props.lastName}!</div>
    )
}

Here we've changed HelloWorld to accept a parameter named props. This name is by convention. props represents an object that contains all of the data passed down to the component, in this case, the firstName and lastName values.

So how do those values get passed to the component? Simple. We add them as attribute-like arguments to the JSX element!

ReactDOM.render(
    <HelloWorld firstName="Dave" lastName="Fancher" />,
    document.querySelector("#container")
);

With the above modifications in place save the file and observe how the component renders the name you supplied.

Hello Props Expressions

Because JSX is more closely related to JavaScript than it is to HTML, we're not restricted to passing only simple values to our components. For example, if we wanted to pass values from an object to a component we could do something like the following:

var me = { firstName: "Dave", lastName: "Fancher" };

ReactDOM.render(
    <HelloWorld firstName={me.firstName} lastName={me.lastName} />,
    document.querySelector("#container")
);

Of particular importance here is the fact that we replaced the quotes in our HelloWorld definition with curly braces. This tells the compiler that the value for those individual props is the result of an expression - in this case some properties of an object called me. In fact, any valid JavaScript expression can appear within the braces thus allowing for some rather powerful techniques, some of which we'll look at a bit later but to showcase the capability lets extend our example just a bit further.

const getUserInfo = () =>
    ({
        firstName: "Dave",
        lastName: "Fancher"
    });

function HelloWorld (props) {
    return (
        <div>Hello {props.user.firstName} {props.user.lastName}!</div>
    )
}

ReactDOM.render(
    <HelloWorld user={getUserInfo()} />,
    document.querySelector("#container")
);

In this final revision we've made several modifications:

  1. We added a getUserInfo function to return some information about a user. You can imagine this function returning some data from a persisted JWT token or other source.
  2. We revised the HelloWorld component to get the first and last name values from a prop named user
  3. We changed the HelloWorld element to take a prop named user from the result of evaluating the getUserInfo function.

Although the end result will the same as before we've begun the process of building composable React components. Components aren't usually quite as simple as what we've seen in this example though so let's move on to a more complex example.


Module 4: Class Components

In the last section we defined a component as a single JavaScript function but we'll quite frequently not only need our components to manage their data but we may also need more control over the components' lifecycle. That's where component classes come in.

Just like their functional counterparts are based on JavaScript functions, class components are based on JavaScript classes. This approach gives us more control over an individual component's behavior.

From here on, we'll no longer need the HelloWorld component so you can safely delete it from your index.jsx file. Don't worry about losing the example of a functional component because we'll be building up several more as we develop the application. (You can also refer back to the HelloWorld example in the Module3 folder if you'd like to see it again.)

A common task in Web applications is providing a way for users to log in. We're not going to go to the extent of connecting to an actual authentication system but we will fake it by providing a log in form and using local storage to persist that state.

Defining the Login Form

Since we've moved beyond the basic HelloWorld example and are starting to get serious about building this application let's start following some standard conventions for project organization. There are several ways to approach this but to keep things fairly simple we'll organize our code according to purpose and feature. Since the login form will be a component that applies to the whole application we'll place it in /components/app/loginForm.jsx. Go ahead and create that folder and file structure.

After creating that structure the first thing we'll need to do is import some types from the React packages. We can do so with the following line (feel free to copy it from index.jsx):

import React, { Component } from "react";

Because we want React to handle placing the login form on the page we don't need to import ReactDOM. We will, however, have to tell index.jsx about the form but we'll come back to that in due time.

Next we'll create the initial structure for the component (feel free to copy this from the Module4 folder):

export default class LoginForm extends Component {
    render () {
        return (
            <div className="form-inline">
                <div className="form-group">
                    <label htmlFor="username">Email address</label>
                    <input type="text" className="form-control" id="username" placeholder="Email Address" />
                </div>
                <div className="form-group">
                    <label htmlFor="password">Password</label>
                    <input type="password" className="form-control" id="password" />
                </div>
                <button className="btn btn-default">Log in</button>
            </div>)
    }
}

At this point our LoginForm component is conceptually little more than what we saw with the HelloWorld functional component but there are some important differences. First, instead of being a function we've defined a class called LoginForm that extends the base Component class that React provides. Extending Component is required for class components.

Our LoginForm class also currently defines only a render function which returns the content to render. Here we see another implication of JSX being so closely related to JavaScript in that many of the attributes such as className and htmlFor follow the JavaScript naming conventions rather than the HTML conventions.

At this point we've defined enough of the component to add it to the page but in order to do so we must first tell index.jsx about it. We'll accomplish that by adding an import directive to index.jsx

import LoginForm from "./components/app/loginForm.jsx";

Finally, we can replace our old HelloWorld element with a new LoginForm element like this:

ReactDOM.render(
    <LoginForm />,
    document.querySelector("#container")
);

Note that we don't need to make any changes to our underlying index.html document to handle this new component; we simply tell ReactDOM to render the LoginForm within the #container element.

Now go ahead and save your changes and observe the result in the browser.

So we've defined a typical login form as a React class component but it doesn't do anything yet. We could start writing some code in our index.jsx to handle the user interaction but that's not really the React way.

One of the advantages that class components offer over functional components for components that require some type of interaction is that they provide some additional mechanisms for controlling the component's behavior. These mechanisms collectively form the component lifecycle.

The Component Lifecycle

The component lifecycle consists of three phases:

  1. Mounting
  2. Updating
  3. Unmounting

Each of the three phases provides one or more function hooks we can define in our class components to ensure proper behavior.

Mounting

Mounting occurs when a component is being created and ultimately added to the DOM. We can hook into the mounting process at any of four places by defining methods on our component class:

Component constructor
Allows us to initialize the component's state. A component's constructor accepts the initial props object and should always call super(props). We'll discuss more about state in just a bit.
componentWillMount
Obsolete. Provides an alternative place to initialize state but the constructor is recommended.
render
Required. Returns the content that will be added to the DOM.
componentDidMount
Provides a place to write initialization code that requires elements be present in the DOM.

Updating

After the initial rendering, any changes to props or state will trigger an update. Like the mounting phase, updating also provides several places to hook into.

componentWillReceiveProps
Invoked before new prop values are sent to the mounted component.
shouldComponentUpdate
Provides an opportunity to compare the current prop and state values with the incoming values and inform React as to whether the component should be re-rendered by returning true or false.
componentWillUpdate
Gives us a place to perform any pre-update operations.
render
Same as during the mounting phase
componentDidUpdate
Gives us an opportunity to work with the newly updated DOM.

Unmounting

The final stage of the component lifecycle is unmounting. This phase occurs as the component is being removed from the DOM. Unlike the other phases, Unmounting provides only a single hook:

componentWillUnmount
Called immediately before the component is destroyed. This is useful for various cleanup operations much like .NET's IDisposable.Dispose method.

Managing Login: Setting Initial State

Our LoginForm doesn't need to utilize most of the hooks into the component lifecycle but it certainly does need to be aware of the current user context so let's begin building out that functionality starting with the constructor.

constructor(props) {
    super(props);
    this.state = {
        validationError: null,
        username: null
    };
}

This is a pretty typical pattern for a component constructor. The constructor accepts the initial props, passes them up to the superclass (Component), then defines some initial state that's available to the component.

Before we go any further let's take a moment to talk about what state means in React.

State in React

Until now we've been tossing around the term "state" pretty frequently but we haven't really talked about what it means in a React app. Much like props, React state helps determine how a component will render. Unlike props, though, React state is private to a component but it can be updated in response to events. Furthermore, although state is private to an individual component, it can be passed to child components as props! This is another key aspect of React's compositional nature.

Let's continue building out our LoginForm component with this in mind. Along the way we'll circle back to this discussion with some special concerns around managing a component's state.

Managing Login: Logging On

In a typical modern Web application a user would enter their credentials and click the "Log in" button which would trigger some event handler to process the action. The same holds true in a React application and the process for setting up that handler is quite similar.

We'll begin by adding a click handler to the button:

// snip
<button className="btn btn-default" onClick={this.handleLogin}>Log in</button>
// snip

Although this may look like the classic approach to hooking up a handler for the click event there is something we should discuss. First, notice how this.handleLogin is wrapped in curly braces. Recall from our earlier discussion that curly braces indicate that the value is the result of an expression. In this case we're telling React that this button's click handler is the component's handleLogin function.

Now let's go ahead and define the event handler as a method on the LoginForm class. We're going to keep things simple here and forego the formality of creating controlled inputs and stick with a more traditional approach to reading form data. As we progress through the workshop we'll cycle back to forms and look at more formal approaches later.

handleLogin(e) {
    var emailAddress = document.querySelector("#username").value;
    var password = document.querySelector("#password").value;

    if (emailAddress && password) {
        localStorage.setItem("username", emailAddress);
        this.setState({ userName: emailAddress });
    } else {
        this.setState({ validationError: "There was a problem logging you in. Please check your credentials and try again." });
    }
}

Although event handlers follow a familiar pattern in React they do behave a bit differently. One important difference is that the event passed to the handler isn't a native browser event but is rather a React supplied type called SyntheticEvent which aims to standardize events across browsers. We won't spend time discussing them much here but should you so desire to learn more you can follow the link above.

The handleLogin event handler is rather straightforward. It simply reads the username and password values from their respective input fields and ensures that they have been supplied. If we have both, we persist the user name to local storage and update the component state to reflect the credentials otherwise, we update the component state to reflect an error condition.

Now save your file, jump over to the browser, open the developer console, and try it out. Provided that you haven't jumped ahead in these exercises you'll quickly discover that clicking the "Log in" button results in an error stating that we can't read property setState of null.

Both references to setState in the handler are against this implying that the setState method belongs to our component. Indeed, React's Component class does define a method called setState so what's going on?

This comes down to how JavaScript deals with the this operator. When we bound the function to the click event in the JSX we simply passed the function without any context such as the class reference. There are a few ways to work around this issue including arrow functions but the React team recommends explicitly binding the method to the instance in the constructor like this:

constructor(props) {
    // snip

    this.handleLogin = this.handleLogin.bind(this);
}

Now when we save the file our button should behave properly... or at least not display any errors in the console.

Debugging: Introducing the React Developer Tools

Since we haven't yet built in any conditional rendering logic to control how the component reacts to changes in its state we can turn to the React Developer Tools to verify that the state is actually changing.

The React Developer Tools are a browser extension that lets us inspect the current state of our React application.

Go ahead and open the browser's developer tools if you don't already have them open then click over to the "React" tab.

You should see a few panels. On the left is the document structure complete with component names. On the right is a listing of the selected components props and state. In more complex applications you can drill into child components and examine their state.

Select the LoginForm element if it isn't already selected and observe how the state currently reflects the state we defined in LoginForm's constructor. Now try to log in without entering a user name or password. You should see the validationError flash yellow and update to reflect our error message. Now try logging in with some combination of credentials.

What happened?

Managing State

You should have seen the username flash yellow to highlight the change but notice how the validationError value is still set from the previous failed login attempt. This illustrates another important concept in React:

State updates are merged into the current state.

This means that when we call setState the passed value doesn't replace the current state but rather merges with it. At first this may seem counterintuitive but this is also a key aspect of composing more complex components.

Speaking of setState...

Earlier we discussed how state in React represents the changeable portions of a component. This is true but we need to take care how we go about updating that state.

The first rule of React state management is to never update state directly either by assigning a new object to this.state or by changing properties of this.state.

Instead, we should always update state by passing a new object to this.setState because that is what instructs React to begin its update phase.

Now that we understand a bit more about React state management, let's go back and fix our event handler:

// snip
if (emailAddress && password) {
    localStorage.setItem("username", emailAddress);
    this.setState({ username: emailAddress, validationError: null });
} else {
    this.setState({ username: null, validationError: "There was a problem logging you in. Please check your credentials and try again." });
}
// snip

Upon saving the document we can try our log in again and observe in the React Developer Tools that the component's state now properly reflects our various, well, states.

It's also worth noting here that state updates aren't necessarily synchronous. Instead, values sent to setState are pushed onto a queue so that React may decide to apply several state updates as a batch. The implication of this is that we shouldn't rely on the current state value(s) when setting state. Instead, we can pass the setState function a function which accepts both the previous known state and props. For example, we could rewrite the first call to setState in the last example as follows:

this.setState(
    (prevState, props) => ({
        username: emailAddress,
        validationError: null
    })
);

We're keeping things simple in this example and not taking dependencies on previous states so we'll stick with the basic form.

Decoupling from the DOM

As we just observed, our handleLogin function now reads our entered credentials but we've introduced a new dependency - reading those values from specific elements. For instance, to get the entered email address we use document.querySelector to locate an element with an ID of username. This certainly works but wouldn't it be nice to remove that dependency and give the LoginForm component a reference to our two text boxes? This is where the ref attribute comes in handy.

The ref attribute is a React feature that accepts a callback function which we can use to capture a reference to a DOM element. The supplied callback is invoked both when the component is mounted and when it's unmounted. React will pass either the created DOM element or null depending on when in the component's lifecycle the callback is being invoked.

Let's see ref in action. We'll begin by modifying our JSX to include ref attributes so we can capture the DOM elements as properties (not props) of our LoginForm class.

// snip
<div className="form-group">
    <label htmlFor="username">User Name:</label>
    <input
        type="text"
        className="form-control"
        placeholder="Email Address"
        ref={element => this.username = element} />
</div>
<div className="form-group">
    <label htmlFor="password">Password: </label>
    <input
        type="password"
        className="form-control"
        ref={element => this.password = element} />
</div>
// snip

React handles binding the callback to the component class so there's no need for us to manually bind anything in our constructor or add additional lifecycle functions.

Now getting the value from the text boxes is simply a matter of accessing the class properties rather than performing a DOM lookup.

handleLogin(e) {
    var emailAddress = this.username.value;
    var password = this.password.value;
    // snip
}

As you can see, this approach simplifies how we can get data from our form elements and even gives us the opportunity to change the underlying elements with minimal impact to the rest of the code. React refers to this approach as "uncontrolled" because we're working with the form elements in a more traditional manner without tapping into React's lifecycle. The downside of using uncontrolled input is that we lose the ability to react to changes in those controls. For simple forms such as our login form, the uncontrolled approach is ideal but we'll look at a more "React-y" way of managing forms a bit later.

Managing Login: Conditional Rendering

We've progressed far enough that we can start making our component react to the various state changes. For this component we need to handle both failed and successful login attempts.

Recall from our earlier discussion about React's lifecycle that changes to state trigger the updating phase. This phase ultimately results in rerendering the component. Let's see this in action by implementing a few of the lifecycle functions before updating the render function to display the appropriate content for our state.

export default class LoginForm extends Component {
    // snip

    componentWillMount() {
        console.log("componentWillMount");
    }

    componentDidMount() {
        console.log("componentDidMount");
    }

    componentWillReceiveProps() {
        console.log("componentWillReceiveProps");
    }

    componentWillUpdate() {
        console.log("componentWillUpdate");
    }

    componentDidUpdate() {
        console.log("componentDidUpdate");
    }

    render() {
        console.log("rendering");
        return (
            // snip
        );
    }
}

Upon saving the file the page will refresh and we should immediately see the following in the console:

componentWillMount
rendering
componentDidMount

Now click the log in button and observe the updating phase functions get written to the console:

componentWillUpdate
rendering
componentDidUpdate

We should also see the same messages written to the console for a successful log in.

Feel free to remove these functions as we included them only to observe the lifecycle and won't be using them for anything else.

Since the updating phase functions are called each time the state changes we can provide all of the display logic our form requires within the render function. Let's do that now by first handling a successful login attempt.

render () {
    if (this.state.username) {
        return <div>Hello, {this.state.username}!</div>;
    }

    return (
        // snip (same JSX as before)
    );
}

Entering some text in both fields and clicking the log in button should now result in the welcome message being displayed instead of the log in form.

The beautiful thing about this process is that React applies the changes intelligently; it applies the changes against an in-memory copy of the DOM and only applies what changes to the browser.

Now let's handle displaying the validation error. We really need to make a small change but remember, the render function must return exactly one item so we'll need to first wrap the current JSX within another div.

render() {
    if (/* snip */) {
        // snip
    }

    return (
        <div>
            // snip
        </div>
    );
}

Now all that's left is to include an expression that returns either the validation error or null within our new div.

<div>
    {this.state.validationError
        ? <div className="alert alert-danger">
            {this.state.validationError}
            </div>
        : null }
    // snip
</div>

Finally! We now have a component that properly handles our fake login and displays either the welcome message, the form, or the form and error message depending on our previous actions.

Reviewing the code really reveals how easy it is to create intelligent, declarative, and fully-encapsulated components within React.

As powerful as our simple LoginForm component is, it's still missing a few things. First, recall that when we first created the handleLogin function we had the successful case persist the email address to local storage. The purpose of doing that was to maintain our user context between page loads but if we refresh the page we see that our application has apparently forgotten who we are. Let's fix that.

All we need to do is update our constructor logic to initialize the state's username property to the value in local storage.

constructor(props) {
    super(props);
    this.state = {
        validationError: null,
        username: localStorage.getItem("username")
    };

    // snip
}

Now save the file. If you were already logged on when the page refreshed you should still see the welcome message. If not, log in then refresh the page.

This leaves us with a problem. How do we clear that user context? In other words? How do we log off?

Logging Out

Logging off itself is pretty straightforward. We know we'll need a button of some sort and another event handler. Let's start this feature by defining the event handler.

handleLogoff(e) {
    localStorage.removeItem("username");
    this.setState({ username: null, validationError: null });
}

Remember to ensure that the function is properly bound to the component class by adding the following to the constructor:

this.handleLogoff = this.handleLogoff.bind(this);

With that in place let's take this opportunity to introduce a few more React concepts by using an anchor element instead of a button.

render() {
    if (this.state.username) {
        return (
            <div>
                Hello, {this.state.username}! (<a href="#" onClick={this.handleLogoff}>Log Off</a>)
            </div>
        );
    }

    // snip
}

By now this pattern should be starting to look familiar. We add the JSX that represents some HTML elements and attach an event handler to the link's click event.

Go ahead and try out new functionality. You should observe that clicking the Log Off button clears our user context but did you notice that the URL also changed? That's because we used a link and allowed the default action to occur. Fixing this issue is a matter of modifying our event handler to suppress that as follows:

handleLogoff(e) {
    e.preventDefault();
    // snip
}

Remember that although this resembles the browser's native event, we're still working with React's SyntheticEvent to ensure consistent behavior across environments.

Custom Styling

So far we've done very little in the way of styling our content and what we have done has relied on the default Bootstrap 3.3 classes. JSX does support an alternative which may be useful in certain circumstances so let's take a quick look at that now by customizing the look of our log off button.

We'll do so by defining a new style object within our existing render function.

render () {
    if (this.state.username) {
        var buttonStyle = {
            border: "1px solid #AA0000",
            borderRadius: "5px",
            color: "#AA0000",
            fontSize: "8pt",
            fontWeight: "bold",
            padding: "7px",
            textDecoration: "none",
            textTransform: "uppercase"
        };

        return ( /* snip */ );
    }

    // snip
}

Notice how all of our new object's properties correspond with the JavaScript-friendly version of various CSS attributes. That's because React will use those properties to set the appropriate styles on the rendered element once we tell it which object to use. We do so like this:

render () {
    if (this.state.username) {
        // snip

        return (
            <div>
                Hello, {this.state.username}! <a href="#" onClick={this.handleLogoff} style={buttonStyle}>Log Off</a>
            </div>
        );
    }

    // snip
}

Now when we save the file we should see the button style update to reflect what we specified with the style object.

Like anything else, this approach has positive and negative aspects. It's good for keeping the styles with the components but short of passing certain styles around through props it feels rather limiting. It also doesn't easily support various underlying pseudo-classes such as a:hover which dramatically diminishes its utility.


*Review Module 4:

  1. Compare the various lifecycle methods. How can they help when building a user interface?
  2. Which lifecycle methods will be triggered by calling setState()? In what order?

Module 5: Differentiating Components

So far we've worked through creating the project from scratch by importing the appropriate dependencies, defining a simple HelloWorld component, seeing how props and state relate to components, and building out a simulated login form. Now it's time to move on to the heart of the application.

Adding a Home Page

Recall from earlier when we created our LoginForm component we instructed React to render the component to the page in index.jsx as follows:

// snip

ReactDOM.render(
    <LoginForm />,
    document.querySelector("#container")
);

When we did this, the LoginForm component was all the application had to render but an application that does nothing but let a user log in or out isn't particularly useful so let's start making this feel like a "real" application by adding a basic home page.

Create a new home folder under components then add new file called home.jsx in that folder.

We need to import React and Container to define the home page component but since all we'll be rendering is some basic HTML and won't have any additional dependencies that's all we'll need.

import React, { Component } from "react";

Now we can define the component. This component won't need to tap into the React lifecycle so a function component is sufficient.

export default function Home () {
    return (
        <div>
            Welcome!
        </div>
    )
}

With that defined we now need to tell React to render it so let's jump back over to index.jsx to make that change.

Remember, when React only works with one element at a time regardless of whether that element is returned from a function component, a class component's render function, or being passed to ReactDOM.render so we have a bit of a problem - ReactDOM.render is already acecpting an element - our LoginForm.

We've already seen one way to work around this in previous examples. We could simply wrap a div around Home and LoginForm but we're at a point where it's more important to question whether that's the right thing to do.

Container and Presentation Components

As our application grows it becomes increasingly important to think about how its individual pieces interact with each other; ultimately we need to think about how the application is composed.

One separation that tends to naturally fall out of dividing the application into focused components is that of container and presentation components.

Container Components
Container components are responsible for managing data and responding to state changes. They still provide the required render function but only to return another component.
Presentation Components
Presentation components are concerned what's actually being rendered based on the props supplied by a container component. These components generally have very little logic associated with them and as such are often defined as function components.

We know our application is going to be more involved than a single page so it's really a good time to start thinking about our components in those terms and how they fit within our application.

We could split our LoginForm component into two but it's pretty straightforward as it is and although refactoring it would be rather trivial let's focus our efforts on another area, namely, a container component for our app.

Create a new folder named containers under the dev folder. Since this will be the root component for our application we'll just create a new file called appContainer.jsx in that new folder. We'll be needing the requisite React import so go ahead and add that to the new file, too. (This file will be a bit sparse for now but we'll definitely be coming back to it a few times throughout the rest of this workshop.)

Since the whole point of creating a container component is to manage state among other things we'll want to define this as a class component.

export default class AppContainer extends Component {
    render () {
        return null;
    }
}

This is a great start for our new container but right now it doesn't do anything. If we were to replace the LoginForm element in our index.jsx file right now we'd see nothing because AppContainer.render returns null. It is important to note though that AppContainer still extends Component. This is because React makes no distinction between container components and presentation components - they're both components as far as React is concerned so the distinction is purely an architectural pattern.

Let's have this return some content by first importing the two components we're going to render - LoginForm and Home.

import LoginForm from "../components/app/loginForm.jsx";
import Home from "../components/home/home.jsx";

Now let's make AppContainer return something more useful than null.

render () {
    return (
        <div>
            <LoginForm />
            <Home />
        </div>
    );
}

And finally, let's replace references to LoginForm in index.jsx with the AppContainer.

// snip
import AppContainer from "./containers/appContainer.jsx";
// snip

ReactDOM.render(
    <AppContainer />,
    document.querySelector("#container")
);

Saving the file should again force the browser to refresh with the newly compiled changes now rendering both the LoginForm and Home content.

Again, though, while we've wrapped our app into a container component and are now displaying content we're still not really doing anything useful. Since most modern applications get data from an API how about we create another component to do just that?


Module 6: External Data

At the beginning of this workshop we saw a preview of the completed project. In that demo we showcased some data from the publically accessible, open source API: An API of Ice and Fire, a fan created and maintained database of information pulled from the Game of Thrones TV series and Ice and Fire novels. In this section we'll begin connecting to that API so we can show its data.

The API puts data in several categories:

  • Characters
  • Houses
  • Books

We're going to focus on the character data today but the others would be excellent opportunities to continue experimenting with React on your own.

Getting External Data

One of the most beautiful things about React is that it is a highly focused library. Unlike other libraries, React focuses on one thing - building declarative user interfaces - and does that one thing very well. Of course, what this means is that there are a great many things that fall outside of React's scope and working with external data is just one of those things that React isn't directly concerned with. As such we're free to select whichever approach suits our needs best.

For working with external data there are plenty of options available. Perhaps an application already has jQuery and is using $.ajax (Note: mixing React and jQuery is generally a bad idea but can work for some things). Maybe the Fetch API is a good fit because we know our users will be using browsers that support it. Maybe a plain ol' XmlHttpRequest is more than sufficient. The point is React doesn't dictate how we get that data. React only cares how to render it.

For our purposes in the workshop we're going to use the axios library. Axios provides a simple interface quite similar to that offered by jQuery's ajax function but like React, axios is focused on just its one task - making network requests. It's also promise-based so it's very easy to wire-up the appropriate callback functions.

Installing Axios

We can install axios by running the following terminal command:

npm install axios --save

Now we're free to reference axios within our application. For convenience, we're going to define our interactions with axios, and the Ice and Fire API by extension, in a separate module, outside the context of any controller and simply provide an interface of sorts. This allows us to keep our modules concerned with only manipulating the DOM and it gives us a really easy way to replace that functionality later when we want to save a lot of typing!

Create a new file in the dev folder called iceAndFireRepository.js. Note that we've used the .js extension to indicate that this is just a JavaScript module and not a React component.

We're going to skim over many of the implementation details here as they're more related to the Ice and Fire API than React or even axios. Every API comes with its own requirements and interfaces and since we're using this particular API for demonstration only it's not really important to cover them in detail. What is important is the overall process of interacting with an API.

The first thing we'll do is import the axios package.

import axios from "axios";

Next up is to define a few constants we'll use to access the API. These identify the URL and tell the API which version we want to use.

const API_URL = "https://www.anapioficeandfire.com/api/";
const API_VERSION_HEADER = "application/vnd.anapioficeandfire+json; version=1";

const API_REQUEST_HEADER = {
    "headers": {
        "accept": API_VERSION_HEADER
    }
};

const API_CATEGORY = {
    CHARACTERS: "characters"
};

Now we can use axios to get some data. Given the URL structure that this particular API uses we can write some generalized functions to handle requests and export a function to get a page of character data.

const get =
    url => axios.get(url, API_REQUEST_HEADER);

const getPage =
    category =>
        (page, pageSize) =>
            get(`${API_URL}/${category}?page=${page}&pageSize=${pageSize}`);

export const getCharacters = getPage(API_CATEGORY.CHARACTERS);

There are a few arrows in there but the code is really just breaking down the process of making the request into some composable units. The get function accepts the URL we're having axios GET for us. The getPage function is a higher-order function that accepts the data category (character, house, book) and returns another function which accepts pagination information, constructs a URL, and forwards it on to get. Finally, the getCharacters function is a partial application of getPage specifically tailored to getting character data.

Ultimately, when we invoke getCharacters we're given an axiosPromise object which conforms to the ES6 promise spec so we can handle the asynchronous operation as we might expect.

This highly functional approach to getting data really isn't all that different from how we compose React components and is indeed the model upon which React composition is based. Our data flows into one function (getCharacters) which adds some additional data to flow into another function (getPage) which formats a URL and forwards it on to another function (get) which in turn makes a request via axios.get. Data always flows in one direction through the chain ultimately resulting in that promise.

Now let's build out a controller to observe this in action.

Our First Request

Create a folder named characters under the components folder and in that folder create a file named characterList.jsx. All the usual setup applies but we also need to import our new iceAndFireRepository

import * as IceAndFire from "../../iceAndFireRepository.js";

Next up is to define the component. We'll definitely need to tap into the React lifecycle on this one so let's define it as a class component, making sure to declare some initial state. Since this component is going to represent a list of characters returned from the API it seems like an array named characters would be a good starting point.

export default class CharacterList extends Component {
    constructor (props) {
        super(props);

        this.state = {
            characters: []
        };
    }

    render () {
        return null;
    }
}

For now we're not going to render anything. This will give us another good opportunity to use the React Developer Tools and observe some behavior which we can use to drive the rendering logic.

Where in the CharacterList component should we invoke our API request? A moment ago we mentioned needing to tap into the React lifecycle and the lifecycle documentation gives us a pretty clear answer:

"If you need to load data from a remote endpoint, [componentDidMount] is a good place to instantiate the network request."

We should initiate the request in componentDidMount.

componentDidMount () {
    IceAndFire
        .getCharacters(1, 25)
        .then(
            response =>
                this.setState({
                    characters: response.data
                }));
}

Our componentDidMount function consists of a single expression which initiates the request for the first page of 25 characters. When the request is complete we simply update the component state to reference the returned list.

Now add the component to the page in index.jsx and go to the browser. The visible page content shouldn't change but something has happened behind the scenes which you can observe in the React Developer Tools.

Observing the State Change

Because we didn't have much interesting to look at when we examined our LoginForm's state, let's repeat that exercise with our slowly growing page.

This page now consists of several layers of components and elements. When the page loads, the React Developer Tools will now allow us to drill into the overall structure starting at the top level: the AppContainer. By default this node is selected and the tools report that there are no props, or, more specifically, that the props are an empty object. Let's drill into the child components by expanding the AppContainer element.

If we select the LoginForm element we'll see the same state values we observed before. If you're logged in you'll see the username set otherwise you'll see null for that value.

The Home component is simply displaying a welcome message so there's nothing interesting to look at there but what about our new CharacterList component? When we initialized the state in its constructor we explicitly set the characters value to an empty array but the developer tools are showing us very clearly that it's an array of 25 items! These were populated when our API request completed and se handed them over to the component via the call to setState!

Data Munging with Axios

Now that we've observed that we're definitely getting data back from our API request we're close to extending the CharacterList component to write out that list of characters.

One thing to note about the data from this API is that it isn't necessarily the cleanest data around. Take a moment to drill into the characters array in the developer tools. You should pretty quickly find that a great many characters are missing a name but fortunately those characters generally have at least one item in their aliases collection. In fact, there are lots of subtle things in the results that we could clean up if we continued to hand off the results to our component directly. To simplify our rendering logic we can augment our IceAndFireRepository to handle some of that munging (transformation and other cleanup) so we can keep our component focused on rendering the data.

Just to keep things simple we'll merely return an object that simply contains the character name or alias if the name isn't available.

We'll start by changing the getPage definition to accept a function we'll call munge and iterate over the resulting data, passing each item to that function:

const getPage =
    (category, munge) =>
        (page, pageSize) =>
            get(`${API_URL}/${category}?page=${page}&pageSize=${pageSize}`)
                .then(response => response.data.map(munge));

Notice how getPage is now providing a callback for then rather than simply returning that axiosPromise like it was before. This gives us a convenient way to transform the response before handing it off to the caller. We could also provide a catch callback here in case of an error but we'll leave that for later when we can see a clean way of handling the errors. Regardless, axios still returns a promise to the caller, it will just resolve with the cleaned data rather than the actual HTTP response.

Now let's implement the munging function. It's really complicated:

const mungeCharacter =
    character => ({
        name : character.name || (character.aliases[0] + "*")
    });

All we're doing here is returning a new object based on the supplied character. If character.name is falsy we return the first alias with an asterisk signifying that we didn't find a name.

Next we'll pass the mungeCharacter function to getPage from the getCharacters function.

export const getCharacters = getPage(API_CATEGORY.CHARACTERS, mungeCharacter);

Finally we need to make a change to our CharacterList component because we've changed the structure of the data returned by the promise. Now we simply need to set the characters state value to response rather than response.data.

That's it! Save the file and observe that the CharacterList state now contains a collection of 25 objects, each of which include a name. Our data is now ready to be displayed!

Displaying the Data

Rendering this data is similar to what we've seen previously except now there's a bit of a twist. So far we've seen rendering only single values but never a collection. Clearly we'll need some type of looping construct but there's slightly more to it than that.

For now let's just render the characters as a bulleted list.

render () {
    return (
        <ul>
        </ul>
    );
}

This sets us up to render the list but would you believe that rendering the items can be accomplished with only one more line? It's true!

render () {
    return (
        <ul>
            {this.state.characters.map(c => <li>{c.name}</li>)}
        </ul>
    );
}

To render the items we just have to map each one to an li element!

Now save the file and observe what happens in the browser's console window. You should see a warning from React stating that each child in an array or iterator should have a unique "key" prop.

We've excluded an important piece of information that React needs to efficiently apply any changes to the underlying model to the DOM. To resolve this we need to add a prop named "key" to each li element. The value we provide should uniquely identify each item. The natural choice for this would be the item's unique identifier but since we didn't include that piece of information in our refined model we'll just set it to the element's index within the collection.

render () {
    return (
        <ul>
            {this.state.characters.map((c, ix) => <li key={ix}>{c.name}</li>)}
        </ul>
    );
}

Finally, saving the file should result in a bulleted list of 25 items and no warning from React.


Module 7: Routing and Navigation

Now our application is starting to take shape. We have a functioning login form and a listing of characters we've retrieved from the Ice and Fire API. We still have quite a problem though - our single-page app (SPA) is just that, a single page. There's nothing in here yet to give the illusion of navigating around pages even though we've clearly separated the Home container from the CharacterList container. Let's fix that by introducing a new package: React Router.

React Router is a component-based, declarative navigation model which provides an easy way to swap out visible components while providing a deep-linking mechanism for easy, URI-based access to specific parts of our application.

While many popular environments (including previous versions of React Router) primarily focus on static routing, that is, having routes defined as part of the initialization process, React Router takes a fundamentally different approach by handling routes dynamically as components. This means that rather than having to specify every route in one place we can define them where they're used.

There are a few versions of React Router available. The one we're going to use and the one discussed here is version 4.

Like anything else in software there are trade-offs associated with each approach. One of the biggest benefits of the dynamic approach is that our applications are free to evolve but the impact to other sections of the application from adding and removing features is kept to a minimum.

There are only a few key components and concepts that we're concerned with for our application:

Routers
Routers manage transitioning between routes and managing the route history. There are a few different kinds of routers we can use and we'll discuss a few of them a bit later.
Routes
Routes render the content that corresponds to a particular path/URI. By default routes are rendered inclusively - as long as the path matches, the route will be rendered.
Switches
Switches are route groups where only one route will be rendered even if other routes in the group match the path.
Links
Links provide a component-based mechanism for interacting with the router and thus navigating around the application.

React Router is a powerful tool for our React Applications but we're only going to highlight a few of its features here. Be sure to check out the documentation to learn more about its full capabilities.

Now that we know a bit about the router it's time to add it to our application.

React Router: Installation

We install React Router like any other npm package.

npm install react-router-dom --save

This package allows the router to interact with the DOM but also has a dependency on the react-router package which we also need so simply installing it is enough to get the other packages we need for this exercise.

Once the package has been installed we can import it into our app. But where?

React Router: Adding a Router

Recall from before we added axios that we split a container component called AppContainer away from index.jsx. This component is intended to serve as the wrapper for our entire application and Routers are typically defined at the application root, this seems like a natural place.

Add the following import directive to appContainer.jsx:

import { BrowserRouter, Route, Switch, Link} from "react-router-dom";

This import directive makes the specified components available to our appContainer. We'll be using each of them over the next few sections but since the heart of this functionality is the router let's start there.

As you can tell from our freshly added import directive, we're going to use a router called BrowserRouter. This is just one of several routers that React Router provides, each having their own specific focus. For instance, BrowserRouter takes advantage of the HTML5 history APIs while HashRouter uses the hash portion of the URL to support older browsers. There are a few others as well but those are well beyond the scope of this workshop so let's get back to adding the router to our application.

The router is our application's outermost component so we'll activate it simply by wrapping the existing render content in a <BrowserRouter> element like so:

render () {
    return (
        <BrowserRouter>
            // snip
        </BrowserRouter>
    );
}

As a container component, the router doesn't change the look of our page in any tangible way so let's continue on. We're going to use Switches and Routes to split our application into navigable pages.

Let's keep our overall page structure in place but wrap the existing Home and CharacterList components within a new Switch component:

<div>
    <LoginForm />
    <Switch>
        <Home />
        <CharacterList />
    </Switch>
</div>

One interesting point about Switch is that since it returns only the first matching element and we haven't any route constraints, only our Home component will be rendered right now. If we were to switch around the order of the elements within the Switch only the CharacterList would be rendered.

So how do we add constraints to our Home and CharacterList elements? We need to wrap them each within individual Route components and specify the path to which each will resolve.

<Switch>
    <Switch>
        <Route path="/">
            <Home />
        </Route>
        <Route path="/characters">
            <CharacterList />
        </Route>
    </Switch>
</Switch>

For those with some React experience, the above snippet probably looks pretty ugly. Don't worry, we'll clean it up shortly!

Webpack Dev Server: Enabling Deep Linking

Great! Now we have some Routes defined so let's try them out. If you refresh the page you should see the familiar welcome message but what happens when you try to navigate to /characters?

You should see a message stating

Cannot GET /characters

This is the Webpack Dev Server's 404 message. We're building a single-page app and relying on the BrowserRouter and Routes to determine what content to display but when we enter /characters in the browser's address bar the server tries to serve content hosted at that address rather than the root. We can fix this with a single tweak to our start task in package.json.

{
    // snip
    "scripts": {
        // snip
        "start": "webpack-dev-server --open --history-api-fallback",
        // snip
    }
    // snip
}

Now save the file and restart the server (CTRL + C then npm start). When the browser opens try navigating to /characters again and observe the result. You should see the page load properly because the --history-api-fallback option tells the server to respond with /index.html in the event of a 404 thus enabling deep-linking but instead of the character list we see the same welcome message even though we expected to see the character list.

Why?

The reason is that the routes are matched top-down and the matching algorithm looks at path segments. /characters is a child of / so both of our routes match. Since Switch returns the first match even though we wanted the second, Home is rendered instead of CharacterList. The workaround is to add another attribute to the route:

<Route path="/" exact>
    <Home />
</Route>

Now when the matching algorithm inspects the first route it will find that it does match but because it's not an exact match the algorithm will move on to the next route thus rendering the CharacterList component.

React Router: Route Options

Before we add navigation to our page let's return our focus to the Routes we defined a little while ago.

When we defined those routes we specified our Home and CharacterList components as children of the Routes. This is certainly acceptible but you'll likely more often specify them through the component attribute even if for no other reason than it's a bit cleaner.

<Switch>
    <Route path="/" exact component={Home} />
    <Route path="/characters" component={CharacterList} />
</Switch>

We don't necessarily have to specify a component directly, though. The Route component exposes another attribute called render which accepts a function and gives us more programmatic control over what's rendered when the route is matched. All of the route props are also forwarded on to the render function so we're free to use them within the function.

We can see this if we temporarily replace one of our routes with a function.

<Route path="/"
        exact
        render={props =>
            <div>You are viewing the content for: {props.match.path}</div>} />

Our application doesn't need us to do this but it's good to know about so go ahead and undo the change.

React Router: Unmatched Paths

A little while ago our server gave us a 404 when we tried to access /characters so we modified the server behavior to always return /index.html regardless of what was requested and let React Router handle displaying the content based on the path. But what if the user enters something that legitimately doesn't exist within our application?

Try navigating to something like /notfound. What do you see?

You should see the page title and LoginForm but it would be nice to communicate to our user that they've tried accessing something that doesn't exist. We can handle this easily by simply adding a default Route. For fun we'll implement this through the render attribute rather than building a formal component.

<Route render={
    props =>
        <div className="spacerTop alert alert-danger">
            Sorry, the resource you requested ({props.location.pathname}) does not exist.
        </div>} />

Once again try navigating to an invalid path and you should now see the not found message. But what if we try going to something like /characters/20? We'll eventually have this as a valid route but as far as the current state of our application is concerned, this isn't valid so we should see the error, right?

Navigating to that URI still shows the character list because /characters matches a valid route and the 20 is just ignored by router. We have some flexibility in how to solve this but for the time being let's implement the easiest solution: requiring the /characters route to match exactly.

<Route path="/characters" exact component={CharacterList} />

React Router: Navigation

Our application is really starting to feel like a real single-page application. We have several components, routing, and deep linking but so far we have no way to navigate around! How do we expect our users to get from page to page?

In a traditional application we'd link directly to pages with A elements and while our deep-linking capabilities would certainly allow that, React Router offers a better way: Link components. Why might we prefer the Link components over traditional A elements?

The primary reason to prefer the Link components is that we've already loaded the application! If we were to link directly to part of our application that would result in a new HTTP request to the application thus making us (typically) not only have to reload the application but also lose state! The Link components avoid this by working directly with the Router at our application root.

Let's define a function component to represent a navbar. To make this feel like a navbar we'll be mixing some Bootstrap classes with some of our own which are already defined in site.scss. (Feel free to place this in the appContainer.jsx file for convenience.)

const NavBar =
    props =>
        <ul className="list-inline navMenu">
            <li role="presentation">
                <Link to="/">Home</Link>
            </li>
            <li role="presentation">
                <Link to="/characters">Characters</Link>
            </li>
        </ul>

Here we have a function component which renders an unordered list with a few Link components. When rendered, each list item will be displayed as a tab which we can click to jump to different parts of the application.

We still don't have the NavBar on the page so let's do that and while we're at it let's wrap the remaining content in another div to make it feel like tab content.

<BrowserRouter>
    <div>
        <NavBar />
        <div className="iceAndFireBody">
            {/* snip */}
        </div>
    </div>
</BrowserRouter>

Now when we view the page we should see some significant changes. Go ahead and click between the new tabs and observe how React toggles between the routes. For some added effect, open your browser's developer tools to the Network tab and observe that the only HTTP traffic is the character API request as opposed to entering the URIs directly into the address bar!

One thing that's still missing here is a way to highlight the current tab. Bootstrap usually handles this automatically but we're only using Bootstrap styles and not its JavaScript capabilities because React doesn't play nicely with other libraries that manipulate the DOM. Another difference between how Bootstrap and React Router handle identifing the current tab is that Bootstrap would apply a CSS class to the li element whereas React Router wants to apply it to the underlying A element. (Yes, Link renders an A element but hooks up the appropriate event handlers for us.) So how do we identify the current tab?

As always, we have a few options.

The first option, which we'll avoid, is to create a new component which wraps the Link. (There are a number of examples of this approach on the Web.) The alternative is far easier and meets our needs quite well.

We can use React Router's NavLink component instead! NavLink is a specialized Link component which exposes a few additional props which we can leverage to highlight our selected tab.

const NavBar =
    props =>
        <ul className="list-inline navMenu">
            <li role="presentation">
                <NavLink to="/"
                         exact
                         activeClassName="active">Home</NavLink>
            </li>
            <li role="presentation">
                <NavLink to="/characters"
                         activeClassName="active">Characters</NavLink>
            </li>
        </ul>

One of the really nice things about NavLink is that it follows some patterns that we're already familiar with: the exact prop, for instance, behaves as we saw with Switch. The other prop we supply in our revised NavBar, activeClassName, indicates which CSS class should be applied to the underlying element when it's matched. In our case, we have an active class defined in `site.scss`` which affects border, text color, and background color.

React Router: Child Routes

Most applications follow a pattern of presenting a list of data and linking to a detailed view of items in that list. For instance, a user list might show some basic information about each user while linking to more detailed information about individual users. We can follow that same pattern in React applications. In fact, we already mentioned we were going to when we discussed trying to traverse to routes such at /characters/20 which would typically indicate a character with ID 20.

We could simply add a new component and route to AppContainer.jsx and follow the established pattern there but in the interest of separating concerns and showing how React Router v4 dynamic routing works lets isolate the character-related pieces to a separate container component which we'll create in a new characters folder under containers. We'll call this new container component characterHome.jsx.

export default class CharacterHome extends Component {
    render () {
        return null;
    }
}

For simplicity here we're going to stay focused on implementing a child route and not worry about promoting code from the CharacterList component to the CharacterHome component.

This pattern should look quite familiar by now. We create a class that extends Component and implement a render function. We know that we're going to render the CharacterList component at /characters and a yet-to-be-built CharacterDetail component at /characters/:id so that sounds like we need two routes. (Remember to import the appropriate types from the other modules!)

export default class CharacterHome extends Component {
    render () {
        return (
            <Switch>
                <Route path="/characters" exact component={CharacterList} />
                <Route path="/characters/:id" component={CharacterDetail} />
            </Switch>
        );
    }
}

Just as before, we defined routes that reflect the various parts of our application but now we're focused specifically on characters.

One of the interesting aspects of React Router's dynamic routing capabilities is that we actually don't need to be quite as specific as we have been with our route path definitions. As it currently stands we've locked this component into the /characters scheme by hard-coding those paths but by taking advantage of the fact that route props are given to our new CharacterHome we can make this a bit more flexible.

<Switch>
    <Route path={this.props.match.url} exact component={CharacterList} />
    <Route path={`${this.props.match.url}/:id`} component={CharacterDetail} />
</Switch>

By replacing the hard-coded paths with the matched URL we achieve the same result as what we had before except now we're allowing the dynamic routing to tell us where we are within the application. This frees us to change the base URI for this part of the application without having to change this component to reflect its new location.

We haven't defined the CharacterDetail component yet so let's do that now:

const CharacterDetail =
    props =>
        <div className="spacerTop">Details for character: {props.match.params.id}</div>;

export default CharacterDetail;

Let's now connect this new CharacterHome component to our AppContainer. We can do this by replacing the CharacterList import and changing the component prop for the respective route in appContainer.jsx.

import CharacterHome from "./characters/characterHome.jsx";

Finally, let's modify our CharacterList component to render some Links so we can see these new routes responding accordingly. Let's begin by creating a simple function component above our CharacterList.

const CharacterListItem =
    props =>
        <li>
            <Link to={`${props.location.pathname}/${props.character.id}`}>
                {props.character.name}
            </Link>
        </li>;

This component will replace our current in-line list items. Note how we're taking advantage of the route information from the props to dynamically generate the Link target.

Now let's make the CharacterList render this component. We need to replace the current map result with the CharacterListItem as follows:

{this.state.characters.map((c, ix) =>
    <CharacterListItem
        key={ix}
        character={{ id: ix, name: c.name }}
        {...this.props} />
)}

There are a few new things going on here so let's look at them before we continue.

First, we're consolidating a few values into a single prop by passing a new object for the character prop. This is perfectly acceptible here since our repository is only returning a name. Typically you could just pass the object from the repository rather than creating a new object.

The second new thing is something is using the spread operator with this.props to allow all of the CharacterList props to flow down to each CharacterListItem. This allows us to access useful route information such as the location from that component. Using the spread operator here is a bit on the questionable side since components should really only get the data they need but it was as good a place as any to show that it can be done.

With that slight refactoring out of the way, let's return to the browser and follow one of the links.

Uh oh! Instead of the CharacterDetail we're seeing the error message we defined back with our routing!

React Router: Unmatched Paths (Revisited)

Recall when we defined the Route for /characters in appContainer.jsx we included the exact prop. We did that because we didn't have the concept of a detail page yet so it was perfectly valid. Now we do have a detail page and it's at /characters/:id but that route is defined within the CharacterHome component.

Because we require an exact match for /characters in AppContainer, the router won't select the route when we provide the id value thus we never get directed to CharacterHome to discover that route. Let's remove exact from the /characters route and try our links again. We should now see a message stating the id of the character we clicked.

This leaves us with some interesting problems as far as unmatched paths go. First, we've moved detection of unmatched routes from the application root to individual sections within the app. Next, we need to determine what constitutes a valid id within the context of characters. For instance, /characters/1 is a valid path but /characters/arya%20stark may or may not be. We need to take this into consideration when designing our routes.

We've established that our character ids are integer based so how can we reflect that in our route? The answer lies in everyone's favorite subject: regular expressions.

Route paths can be augmented with regular expressions to further control their format. In our case we want to ensure that the character id is at least one digit long so we can tweak the path as follows:

<Route path={`${this.props.match.url}/:id(\\d+)`} component={CharacterDetail} />

In fact, route detection is built upon the path-to-regexp package so anything that package can handle is a valid path as far as React Router is concerned.

Because we've moved unmatched route detection from the AppContainer and it's now possible to enter an invalid character id we should implement an unmatched path route in CharacterHome. Despite the lack of a "global" detection mechanism the ability to have more granular control over the detection is a positive thing.

<Route render={() => <div className="alert alert-danger spacerTop">Invalid character path</div>} />

Let's go back to the application one more time and try a few different URIs to see our route revisions in action.


Module 8: Introducing Redux

As we've seen throughout the exercises in this workshop, state management is an important concept in React applications. For small apps, React's built-in state management is often sufficient but as applications grow, complexity grows with it which often leads to dependency tree entanglements and other maintenance nightmares. Further complicating the issue is that since in "vanilla" React applications, components are responsible for managing their own state which can make it difficult for us to get a complete view of an application's current state within the browser. This is where Redux comes in.

Redux is a powerful state-management library that, while separate from React, fits very well with the React development model. It builds upon Facebook's Flux architecture but also borrows concepts and patterns from another popular front-end framework - Elm.

Redux Architecture

We've already seen that it's entirely possible to build React apps without Redux but Redux definitely makes it easier by providing a simple interface and centralized store for our application's state. (Likewise, it's possible to use Redux without React!) Redux does more than that, though. Redux is an architectural pattern as much as it is a library in that asks us to make some architectural decisions that may seem counterintuitive at first (particularly in Web development) but ultimately lead to a better overall application architecture. Each of these guidelines will be familiar to developers with a background in functional programming.

First, Redux wants us to describe our state in terms of plain objects and arrays rather than complex objects with associated behavior. This keeps our state simple.

Next, Redux wants us to describe changes to the state as plain objects which typically resemble but are separate from our state object. This forces us to think of our interactions as independent actions (or commands for those familiar with CQRS) and what data is associated with those actions while still remaining detached from behavior.

Redux also wants us to express state changes as pure functions, that is, functions without side-effects. This tends to confuse people new to functional programming since the end goal of any software project is to have some effect but by not depending on or affecting external state, functions become deterministic and thus easier to reason about and predict. Ultimately, updates to the central Redux store will be performed in a controlled and predictable manner.

Finally, like React, Redux is unidirectional; data flows in one direction through a series of operations just like a functional pipeline.

Redux Interactions

Redux functionality is divided into three main categories:

Actions
Actions are used to send data to the store. They are represented as plain JavaScript objects and must have a type property to identify which operation they represent.
Reducers
Reducers describe how the application state changes in response to an action. What that response will be is determined by the action's type. Reducers are often composed to handle state changes across different parts of the application.
Stores
Stores hold the current application state and manage changes to that state. Applications will have only one store.

Although there are a few other ways to control how Redux behaves these three pieces (along with the package references, of course) are all that's truly required to get Redux up and running within your application.

Since there isn't really that much to getting started with Redux let's begin adding it to our application. We have a great candidate for our first piece of Redux functionality and that's our LoginForm.

Installing Redux

Before we do anything with Redux in our application we need to add a few more packages to our project.

npm install redux react-redux --save

These packages define both Redux and some integration hooks to make React and Redux work together.

With these in place we can now start defining our actions, reducers and store. It's true that both actions and reducers require a store to work but the store needs to know about the reducers and reducers need to know about actions so we'll begin by defining some actions.

Defining Actions

Actions are how we interact with Redux. They describe what is happening within the system. The terminology here can be a bit confusing because actions typically represent something that has happened, much like an event. Thinking of them as events or possibly even commands can be more convenient.

We're going to hand off state management for our LoginForm operations to Redux. One big reason for doing this is that currently all of our login state is contained within LoginForm. I can think of plenty of reasons that this state should be "promoted" to the application level not the least of which being to share user information with other components!

In designing our Redux integration we need to think about the ways we'll be interacting with the store. In other words, what type of things (actions) would a user take with the LoginForm? Our current design gives us a pretty good indication:

  • handleLogin
  • handleLogoff

These functions both seem like things a user would want to do so lets design our first actions around those. We'll begin by creating a new actions folder under dev. We can then create a new file named authentication.js in the actions folder.

At first glance it might seem like we need only two actions but further inspection of the handleLogin function reveals that we need to handle both success and failure cases so we're actually going to define three actions.

As we'll see over the next few sections, much of the code we'll write for our Redux integration is largely convention-based. There are only a few things that we actually need to do to make Redux work.

One convention is to define constants or variables to hold the various action names so we can reference them without magic strings in both the action and reducer definitions. Grouping these into an object is also acceptible and helps keep the names organized as the application grows.

Let's start creating our authentication actions by defining their names:

export const AUTH = {
    "LOG_IN_SUCCESS": "AUTH.LOG_IN_SUCCESS",
    "LOG_IN_FAILURE": "AUTH.LOG_IN_FAILURE",
    "LOG_OUT": "AUTH.LOG_OUT"
};

We can now refer to these names via AUTH.LOG_IN_SUCCESS, AUTH.LOG_IN_FAILURE, and AUTH.LOG_OUT where ever they're needed.

Now that we know which actions the store is going to respond to we need to think about the data associated with each. For instance, a successful login will need the user's username (represented as an email address) whereas a failure should have an associated error message.

The actions themselves are represented as plain JavaScript objects which we could technically create anywhere but the Redux convention has us encapsulate the object creation within factory functions called action creators to ensure that the action object's shape is consistent.

Here are the creators for our three actions:

const loginSuccess =
    username => ({
        type: AUTH.LOG_IN_SUCCESS,
        username: username
    });

const loginFailure =
    validationError => ({
        type: AUTH.LOG_IN_FAILURE,
        validationError: validationError
    });

const logOut =
    () => ({
        type: AUTH.LOG_OUT
    });

That's it! We now have our initial action creators in place so we can now move on to the reducers.

Defining Reducers

Reducers are responsible for computing the new state based on actions received from the Redux store dispatcher. Like the action creators, reducers are generally rather straightforward and are largely convention-based but there are a few important details to keep in mind which we'll cover as we encounter them.

Following the typical folder convention we'll create a folder called reducers under dev and under that we'll create a new file called authentication.js. Once that file is created, import the AUTH object from the file created in the previous section since those type names are going to be pretty important in just a moment.

import { AUTH } from "../actions/authentication.js";

Now we can begin defining our reducer which, despite its fancy name, is fundamentally little more than a switch statement!

export default function AuthReducer (state, action) {
    switch(action.type) {
        case AUTH.LOG_IN_SUCCESS:
            return state;

        case AUTH.LOG_IN_FAILURE:
            return state;
        
        case AUTH.LOG_OUT:
            return state;
    }

    return state;
}

The AuthReducer function accepts both the current state from the store and the action being applied to the store. It then switches against that required type property to determine how to apply the action to the store, computes the new state and finally returns that new state.

Note that the reducer computes and returns a new state rather than modifying the supplied state. Understanding this distinction is critical for working successfully with Redux. In fact, reducers should be pure functions and never mutate arguments or induce side-effects which can alter the application state.

It's also important for reducers to return the current state if no action applies. This allows the state to flow to other reducers.

So far our reducer isn't actually changing the application state as it simply returns the supplied state so let's build out each action handler.

export default function AuthReducer (state, action) {
    switch(action.type) {
        case AUTH.LOG_IN_SUCCESS:
            return {
                ...state,
                username: action.username,
                validationError: null
            };

        case AUTH.LOG_IN_FAILURE:
            return {
                ...state,
                username: null,
                validationError: action.validationError
            };
        
        case AUTH.LOG_OUT:
            return {
                ...state,
                username: null,
                validationError: null
            };
    }

    return state;
}

The code in each action branch simply creates a new object based upon the current state (via the spread operator) and sets the username and validationError properties as appropriate for the respective action type. In general, this pattern will apply to most reducers you'll write.

Although we've largely completed our first reducer it's still missing one key piece of information: what the initial state looks like. We know based on what we've written that our state should have the username and validationError values but how do those get populated initially such as when the application is being loaded?

The easiest thing to do is to give the state parameter a default value, like this:

const INITIAL_STATE = {
    validationError: null,
    username: localStorage.getItem("username")
};

export default function AuthReducer (state = INITIAL_STATE, action) {
    // snip
}

If the INITIAL_STATE value looks familiar, it's because it was copied directly from the LoginForm constructor. This highlights how well React and Redux work together. Just because we're using a different state management system doesn't mean we have to change how we represent that state.

Defining the Store

At this point we've defined three actions and a reducer. Now we need to define a store to orchestrate the interactions between the actions and reducer as well as manage the current state.

A Redux store is simply an object with some functions attached to it to wire-up mappings between actions and reducers. As such, to create a store all we need to do is invoke the createStore function from the Redux package, passing along a reducer.

We'll create our store at the outermost layer of the application: index.jsx.

const store = createStore(AuthReducer);

If this weren't a React application we'd be good-to-go with using Redux but React doesn't automatically know about Redux so we'll connect the two in the next section.

Connecting Redux to React

Despite the presence of actions, a reducer, and a store within the application, React still needs to be instructed to use Redux but it is a bit of a multi-step process. Let's begin by telling React to use the store we defined in the previous section. To do that we need to wrap our AppContainer component in a Provider component provided by react-redux.

ReactDOM.render(
    <Provider store={store}>
        <AppContainer />
    </Provider>,
    document.querySelector("#container")
);

Pretty simple, right? While this makes React aware of the store, it doesn't tell it how to propagate the state to the rest of the application. To do that, we need to make some changes to our LoginForm component.

First, let's stop exporting the class and rename it from LoginForm to LoginFormImpl. (The actual name here doesn't particularly matter; we're renaming it to wrap it later and return a new LoginForm component.)

export class LoginFormImpl extends Component {
    // snip
}

Now we can begin the process of making this component interact with Redux. This involves defining a function called mapStateToProps to handle extracting values from the store and sending them to the component as props, defining a function called mapDispatchToProps to allow us to pass functions which dispatch the actions to the store, then finally wiring it all up with the connect function.

The mapStateToProps function simply needs to create a new object with only those values that the LoginFormImpl component cares about.

const mapStateToProps =
    state => ({
        username: state.username,
        validationError: state.validationError
    });

Right now all our store contains is our login information but as we bring more data into the Redux store, it'll be increasingly important to allow only those values that components care about to propagate down to prevent unnecessary re-renderings.

Next up is the mapDispatchToProps function. Again, this is responsible for creating function props that know how to send actions to the store via the dispatch function which is the only way to send actions to the store.

const mapDispatchToProps =
    dispatch => ({
        dispatchLoginSuccess: username => dispatch(loginSuccess(username)),
        dispatchLoginFailure: errorMessage => dispatch(loginFailure(errorMessage)),
        dispatchLogoff: () => dispatch(logoff())
    });

Finally, let's wrap LoginFormImpl with the connect function and export the result.

const LoginForm = connect(mapStateToProps, mapDispatchToProps)(LoginFormImpl);

export default LoginForm;

Of note here is that connect is a higher-order function which accepts the various mapping functions and returns a new function which accepts the component to wrap. This can be a bit confusing if you haven't followed this pattern before in other code but it allows us to create a closure for common arguments and wrap multiple components.

Finally, because Redux is handling our state, we can remove everything related to React's state management and replace it with the newly mapped props.

First, remove the initial, default state from the constructor. Do you remember where that's being initialized now?

Next, replace the calls to this.setState in handleLogin and handleLogoff with the corresponding functions we mapped in mapDispatchToProps.

Finally, replace each remaining instance of this.state with this.props. Because we kept the same names for our Redux-managed state we shouldn't have to worry about any additional changes to our login component.

Extending this concept to its extreme, we could move the logic that's in the handleLogin and handleLogoff functions into the action creators and convert LoginFormImpl to a functional presentation component but that seems like a good opportunity for self-study.

Verifying Redux

Redux is now handling all of the state management for LoginForm. How do we know this (aside from that we just wrote it)? The easiest way is to sprinkle some breakpoints throughout the various Redux integration points and observe the code executing within the console but there's a much more interesting way - the Redux DevTools.

The Redux DevTools give us some incredible insights into what's going on in Redux. First, it gives us a log of actions and resulting state, and it gives a chart showing breaking down the current state. What's more is that the Redux DevTools offer a "time-traveling" mechanism which allows us to jump back and forth between previous actions and observe how the React UI changes!

Unlike many other browser extensions, actually using the Redux DevTools extensions isn't simply a matter of installing them; we need our store to opt-in to using them. Fortunately, that just means passing the result of a function call to our store.

const store = createStore(
    AuthReducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

This pattern is pretty typical JavaScript. If we have a function named __REDUX_DEVTOOLS_EXTENSION__ attached to the window object, we execute it. Now save the file and play around with the DevTools for a bit because they're actually pretty amazing. (You may need to force-refresh the page for this particular change to take effect).


Module 9: Asynchronous Actions and Router Composition

We've nearly covered the core concepts for developing applications with React and Redux but in order for our application to more closely resemble a "real-world" application we should have Redux control more than just our login as well as have more interaction with the underlying data.

In the interest of staying focused on working with React we kept our interactions with the Ice and Fire API to a minimum. In keeping with that theme, we're going to fast-forward a bit and use the code from the Module9/Begin folder as the starting point for our examples in this module.

Because we're starting from a common point with the Begin code be sure to both stop any current webpack dev server instances and run npm install to restore the packages. You're free to modify the code within the Begin folder but you may find it preferable to copy the code to leave the original code intact.

Although the basic structure of the app is the same as what we've been building all along there are a number of important changes we should look at before continuing.

If you haven't done so already, go ahead and execute npm start to run this new version of the application.

You should immediately notice one change as soon as the page loads - a "busy" spinner now briefly appears to indicate some background activity. Switch over to the characters tab and you'll also see that instead of a bulleted list we now have a table with much more data and interaction methods! We can control the number of characters on the page, view the details of a character, and even change which page we're viewing.

We're not going to spend much time looking at the implementation details of most of these changes because they're with few exceptions (which we will look at), they're really just extensions of what we've already learned. For instance, the character table is just a new component which emits a table based on data returned from an expanded IceAndFireRepository. There are however, a few interesting things to look at so let's start with the IceAndFireRepository since it's going to play a major role in the rest of this module.

IceAndFireRepository Revisited

In the original incarnation of the IceAndFireRepository when we received the response for a page we transformed (mapped) each item in the response to an object containing the character's name (or first alias). That version of the repository exposed only a single function - getCharacters which, while it met our needs at the time, was rather limiting in terms of what a repository should do.

This new version does much, much more. In fact, even though we can't make any changes to the API data (it's read only) this repository simulates working with a database by pre-loading all of the character data from the API and caching it away in local storage! This provides us with greater query capabilities as well as gives us the opportunity to manipulate that data. (It also lets us go easy on the conference center WiFi after the initial data load!) For an added touch, it's all promise-based so we can simulate delays for network traffic and open the door to dispatching Redux actions asynchronously.

Ultimately there's no magic here. All we've done is extend the foundation we built with the original version. If you're curious about how this version works, feel free to spend some time looking it over.

Enabling Asynchronous Actions

The next key change this version introduces is the ability to dispatch Redux actions asynchronously. Redux doesn't support this capability out-of-the-box so we've added a package called redux-thunk to the project.

redux-thunk is a piece of Redux middleware which we won't go into beyond saying that it extends Redux's base store capabilities.

In order to activate the middleware we needed to import it from the package and pass it to createStore in index.jsx via the applyMiddleware function imported from redux.

Because we also want to continue using the Redux DevTools we can't merely pass the result of applyMiddleware to createStore because createStore can accept only one enhancer but that's OK because enhancers can be composed into a chain via the compose function.

The compose function takes two functions and returns a new function which effectively chains the two functions together such that the result of the first function is passed to the second and so on. In our case, we configure our store with the thunk middleware and allow the configuration to flow into the DevTools extension. This allows us to handle both asynchronous dispatching and interacting with the Redux DevTools.

With the thunk middleware connected to the store we can now pass actions either synchronously as we saw with the LoginForm or asynchronously but what does that look like?

Using Asynchronous Actions

Executing actions asyncronously requires us to change how we think about our action creators. The three action creators we defined for the LoginForm returned had one job - to predictably create the action object. The same holds true with asynchronous actions; we'll define action creators to create the action object but we'll seldom call those action creators directly and instead call them through an intermediary higher-order function which we provide with the action data and returns another function which is given the dispatcher.

The sample project already has an example of this pattern in /dev/actions/app.js so let's take a look at that now.

In general, everything defined in app.js should look familiar. We first define an object to hold our action types. In this case our actions are APP.INITIALIZING and APP.INITIALIZED. These are similar to what we defined in authentication.js and while they identify different actions that we'll dispatch to the store, they represent phases of the same operation - app initialization.

Next we have the appInitializing and appInitialized functions which return the corresponding action objects. These are just like what we saw in authentication.js but unlike those functions we won't be calling these directly but through the initializeApp function which is ultimately responsible for creating and dispatching both actions at the appropriate times.

Here's initializeApp reproduced in its entirety:

export const initializeApp =
    () =>
        dispatch => {
            dispatch(appInitializing());
            return (
                IceAndFireRepository
                    .init()
                    .then(response => dispatch(appInitialized()))
            );
        };

Unlike the standard action creators, initializeApp returns a function that accepts the dispatcher. We'll look at how that returned function is invoked a bit later.

Ultimately initializeApp app is responsible for dispatching the APP.INITIALIZE action, initiating an asynchronous operation to pre-load some data, and, once that operation completes, dispatching the APP.INITIALIZED action.

So how do we initiate this initialization? The answer to that lies in /dev/containers/appContainer.jsx.

Once again, most of the code should look rather familiar. We still need connect to wrap our component and we still use mapStateToProps to extract the various pieces of state we need from the store to props that the component cares about but rather than using mapDispatchToProps we use an alternate syntax where we simply pass an object consisting of the dispatching functions. Notice that we're not explicitly specifying property names here. Instead we rely on the compiler to infer the name for us.

The only function we need to pass to connect is the initializeApp function we discussed. We then invoke that function within the App component's constructor but where's the reference to the dispatcher?

A few moment ago we discussed how the initializeApp function is a higher order function which accepts the data needed for the action and returns a function which accepts the dispatcher. Surely we need to give the function a dispatcher, right? We actually already have by virtue of how we've mapped the function to a prop.

Redux actually wraps the function in a separate function that provides the dispatcher for us. If you were to put a breakpoint in the constructor and inspect the value of props.initializeApp you'd see something like the following:

Ć’ () {
    return dispatch(actionCreator.apply(undefined, arguments));
}

Somewhere within the Redux code, our action creator was wrapped in this function and referenced which was added to the props thus hiding the complexity of handling the dispatching. Pretty clever, huh?

Reducing Asynchronous Actions

Defining a reducer for asynchronous actions is identical to defining reducers for synchronous actions. In fact, the reducer doesn't care at all whether the action was dispatched synchronously or asynchronously.

This is another reason why it's so important for our reducers to be pure functions. Since actions can be dispatched either synchronously or asynchronously the reducers should have no dependency on the current state of the system; they should depend on nothing more than the state and action passed in nor should they attempt to change the state of the system in any way to maintain its integrity.

In the case of the AppReducer (defined in /dev/reducers/app.js) we've kept things simple, APP.INITIALIZING sets isInitialized to false while APP.INITIALIZED sets isInitialized to true.

Now to see this all in action, make sure the server is started (npm start) and observe how when we first load the application we're presented with a message stating that our content will be available shortly along with a spinner. This message quickly disappears as the initialization completes and the APP.INITIALIZED action is dispatched.

Composing Reducers

As an application grows we'll gradually add more and more items into the store. Over time this can make managing the state complicated because remember, Redux uses only a single store.

To mitigate this problem we can turn to a technique called reducer composition whereby we create multiple reducers to handle various parts of the application and combine them via the combineReducers function.

Like the asynchronous actions, we already see this pattern emerging within our application. We have both an AppReducer and an AuthReducer which handle actions for different parts of the application but are based on the same store.

As mentioned, we use the combineReducers function to combine the reducers into a single reducer chain. We can see this in the application by opening /dev/reducers.js.

In this file we import the combineReducers function along with each of the reducers we want to combine. We then compose a reducer chain by passing each reducer along to combineReducers as an object just like we did with our last connect example.

With the rootReducer composed we can import that into dev/index.jsx and pass it to createStore instead of any individual reducers.

One interesting aspect to combining reducers is that each reducer essentially manages its own section of the store. For example, when we combined the reducers we passed them in as app and auth, respectively. This results in the underlying store being segregated into sections named app and auth to keep those concerns separated.

Managing Characters with Asynchronous Actions and Independent State

Now that we've seen how to define asynchronous actions, how to write reducers to handle asynchronous actions, and how to combine multiple reducers into a single reducer chain, let's work through extending the app to handle characters, beginning with some actions.

Create a new file named characters.js in the /dev/actions folder. In that file import the IceAndFireRepository since we'll be using it to drive dispatching some asynchronous actions.

We'd like our list page to show the busy indicator whenever we load a page of data be it from the initial request, changing page size, or changing page. We can accomplish this with a single high-level action which we'll generically call FETCH_CHARACTERS. We can subdivide this high-level action into three additional actions to represent whether the request started, completed successfully, or failed.

For convenience we'll define a small helper function to compose the action types for us.

const createRequestTypes = prefix =>
    ["REQUEST", "SUCCESS", "FAILURE"]
        .reduce(
            (obj, type) => { obj[type] = `${prefix}_${type}`; return obj; },
            {});

The createRequestTypes function transforms an array of three values into an object with properties named after the array items and a string value composed of both a prefix and the array value.

We'll use this to define the action type names:

export const FETCH_CHARACTERS = createRequestTypes("FETCH_CHARACTERS");

The result of this function is a type that looks like this:

{
    "REQUEST": "FETCH_CHARACTERS_REQUEST",
    "SUCCESS": "FETCH_CHARACTERS_SUCCESS",
    "FAILURE": "FETCH_CHARACTERS_FAILURE"
}

Next we can define some action creators for these three action types. They will look similar to what we saw with our app initialization action creators but two of these will accept some data which will be passed along to the store and reducer.

const fetchCharactersRequest =
    () => ({ type: FETCH_CHARACTERS.REQUEST });

const fetchCharactersSuccess =
    response => ({
        type: FETCH_CHARACTERS.SUCCESS,
        characters: response.characters,
        pagination: response.pagination
    });

const fetchCharactersFailure =
    error => ({
        type: FETCH_CHARACTERS.FAILURE,
        error: error
    });

Finally we can create the fetchCharacters function which orchestrates when each of the above action creators are invoked.

export const fetchCharacters =
    pageInfo =>
        dispatch => {
            dispatch(fetchCharacterRequest());
            return (
                IceAndFireRepository
                    .characters
                    .get(pageInfo)
                    .then(response => dispatch(fetchCharactersSuccess(response)))
                    .catch(err => dispatch(fetchCharactersFailure(err)))
            );
        };

Soon we'll update our characterHome component to no longer call the repository functions directly but instead use this action passed in through the component's props.

Our reducer also follows the familiar pattern. We'll define it in a file named characters.js in the /dev/reducers folder. Here it is in its entirety:

import {
    FETCH_CHARACTERS,
} from "../actions/characters.js";

const INITIAL_STATE = {
    loading: true,
    characters: [],
    pagination: {
        page: 1,
        pageSize: 25,
        first: {},
        next: {},
        prev: {},
        last: {}
    },
    errorMessage: ""
}

export default function (state = INITIAL_STATE, action) {
    switch(action.type) {
        case FETCH_CHARACTERS.REQUEST:
            return {
                ...state,
                loading: true,
                errorMessage: ""
            };

        case FETCH_CHARACTERS.SUCCESS:
            return {
                ...state,
                loading: false,
                errorMessage: "",
                characters: action.characters,
                pagination: {
                    first: action.pagination.first,
                    prev: action.pagination.prev,
                    next: action.pagination.next,
                    last: action.pagination.last,
                    page: action.pagination.page,
                    pageSize: action.pagination.pageSize
                }
             };

        case FETCH_CHARACTERS.FAILURE:
            return {
                ...state,
                loading: false,
                characters: [],
                errorMessage: action.error.message
            };
    }

    return state;
}

The only real difference of note between this reducer and the reducers we've seen before is that we have more state that we're managing. All of the other concepts apply exactly as with those reducers.

Now let's combine this reducer with the others by adding it to /dev/reducers.js. All we need to do there is import the type and add it to the object following the same patterns that are already in the file.

Because we're already creating our store with the rootReducer defined in /dev/reducers.js there are no changes necessary in /dev/index.jsx.

Finally, let's connect the CharacterHome component to Redux and take advantage of the store, actions, and reducers starting with defining the mapStateToProps function.

const mapStateToProps =
    state => ({
        characters: state.characters.characters,
        pagination: state.characters.pagination,
        loading: state.characters.loading,
        errorMessage: state.characters.errorMessage
    });

Notice how we're reading each value from an object in the state called characters. This name corresponds with the name we gave the reducer when combining it with app and auth. We could add that entire object to the state but by explicitly adding each value we retain greater control over what triggers re-rendering the component.

Now we can connect the component to the store as shown below. (Be sure to import the fetchCharacters function!)

export default connect(mapStateToProps, { fetchCharacters })(CharacterHome);

At this point we're ready to wire-up the final piece - invoking the asynchronous action from the CharacterHome component! We want to be a bit careful about what we rip out as we make this transition, however, because part of the component state is still being managed by React and not Redux.

First, replace the initial state in CharacterHome's constructor with the following:

this.state = {
    character: {
        name: "",
        aliases: []
    }
};

This revised initial state showcases how we can mix React state management with Redux which may be appropriate for some scenarios. It seems that individual characters should also be managed by Redux doing so would be a great exercise for self-study.

Now update getPage to not call the repository directly but instead invoke the fetchCharacters function from the component's props.

Finally, let's update the characterTable JSX to account for the various states.

render () {
    if (this.props.loading) {
        return <Loading />;
    }

    return (
        <div>
            <ErrorMessage message={this.props.errorMessage} />
            <!-- snip -->
        </div>);
}

We finally have everything in place to asynchronously dispatch and handle Redux actions for getting pages of data. When the request is active loading spinner then, when complete, we return the user to the updated table.


Module 10: Higher-Order Components

In our example app, we have a LoginForm component that either returns a form to allow the user to login:

<div className="form-inline">
    <div className="form-group">
        <label htmlFor="username">User Name:</label>
        <input
            type="text"
            className="form-control"
            placeholder="Email Address"
            ref={element => this.username = element} />
    </div>
    <div className="form-group">
        <label htmlFor="password">Password: </label>
        <input
            type="password"
            className="form-control"
            ref={element => this.password = element} />
    </div>
    <button className="btn btn-default" onClick={this.handleLogin}>Log in</button>
</div>

Or a welcome message if the user is already logged in:

<div>
    Hello, {this.state.username}! <a href="#" onClick={this.handleLogoff} style={buttonStyle}>Log Off</a>
</div>

Right now, the rest of the app works exactly the same whether or not the user is logged in. Let's add an authentication check that a user is logged in before they can access the /characters route.

Since we are using localStorage to store login credentials right now, we can retrieve the username from there to check whether or not they are logged in: localStorage.getItem("username");.

The only thing left to do would be to redirect the user if they try to navigate to an authenticated route. Since most apps will have multiple routes where they have to check for authentication, it makes sense to make this functionality a separate and reuseable component. Basically, we need a function that takes in any component we pass it, enhances the component by adding authentication checks and re-routing, and then returns a new component.

export default function(AuthComponent) {
    class IsAuthenticated extends Component {
        componentWillMount() {
            const authenticated = localStorage.getItem("username");
            if (!authenticated) {
                // re-route the user
            }
        }

        render() {
            return <AuthComponent {...this.props} />
        }
    }

    return IsAuthenticated;
}

That's basically the idea of HOCs. Now any component can be passed into the function as AuthComponent so we can use this as a wrapper directly in our router:

<Switch>
    <Route exact path="/" component={Home} />
    <Route path="/characters" component={AuthWrapper(CharacterHome)} />
</Switch>

*A Note about react-router. In order to get access to this.props.router, a component must be wrapped with withRouter (an HOC from react-router). Once wrapped, the component can dynamically route: this.context.router.history.push('/'); and perform actions with routes like push, pop, replace, etc.

Module 11a: Testing Basics with Jest

Jest is used by Facebook to test all JavaScript code including React applications. One of Jest's philosophies is to provide an integrated "zero-configuration" experience. We observed that when engineers are provided with ready-to-use tools, they end up writing more tests, which in turn results in more stable and healthy code bases. --Facebook

Unit testing is important to ensure the continued functionality and reusability of our components. Jest is a full-featured testing framework that comes with an assertion library built in.

The only command you need to install Jest and start writing tests is:

npm install --save-dev jest

Then, in the package.json file, add a test command:

"scripts": {
    "test": "jest"
},

Now, all of our unit tests can be run with npm test. Jest automatically finds and runs any tests that are in a __tests__ folder or in a file with a .test.js extension.

Test Driven Development

Test Driven Development (TDD) is the practice of writing tests first before the application code is written. The idea is to write a test that will fail and then write the code to make that test pass and then keep repeating that process.

A simple example - We need a function that will be able to sum two numbers, a and b, so we first write the test for that function:

// app.test.js
test('sum function adds two numbers', () => {
  const result = sum(3, 2);
  expect(result).toBe(5);
});

Of course, if the test suite is run now it will fail because the application code hasn't yet been written. Since we have the test depicting what the function should do, we can go ahead and write the sum function:

// app.js
export const sum = (a, b) => {
  return a + b;
};

Now the test passes and we can proceed writing the next test. Of course these examples aren't really practical. Let's get into the ways we can test our React application we are building.

Module 11b: Testing React and Redux

Testing React Components

The first and most basic test for React Components is checking whether or not a component renders correctly. To help with rendering, there is a library called enzyme with a lot of helper functions for testing React applications - it provides a jquery-like syntax and works very well with Jest and other test suites.

npm install --save-dev enzyme

To test that components render, there are two basic methods we can use, full DOM rendering and shallow rendering. Shallow rendering just renders the component, not any child components, so that will work for this test. To mimic a component rendering to the screen without having an actual DOM, we can use the shallow function from enzyme.

const component = shallow(
    <App />
);

This would normally make the component render just fine for the test. However, since the App component is wrapped in Redux, we will get an error when trying to render the component by itself. To make this work, we will have to wrap the component in the Redux Provider as well as pass in our application store.

import { shallow } from 'enzyme'
import { Provider } from 'react-redux'
import { store } from '../src/store/store'

test('App.js renders correctly', () => {
    const component = shallow(
        <Provider store={store}>
            <App />
        </Provider>
    );
})

Now that the App.js component is rendering in the test suite, we can take a snapshot of it and make sure that it doesn't unexpectadly change on us in the future. A snapshot will just record it's the exact state of the rendered component and let us know if the component changes in the future. If it does change, Jest lets us know when we re-run the test suite and give us some option to keep the current state, compare states, or revert the component back to how we had it.

expect(component).toMatchSnapshot();

Testing Redux Actions

Testing actions is usually straight forward as they are simple functions. Just like any other test, we have to call a function and make an assertion about what should be returned. We can hard code the action we expect and then assert that the actions generator matches up using the .toEqual() function.

test('action should match fetchCharactersRequest', () => {
    const action = fetchCharactersRequest();
    expect(action).toEqual({ type: FETCH_CHARACTERS.REQUEST });
});

Now that test passes. We can also test that the helper function works correctly using the same method. We expect that is will create a request type object with REQUEST, SUCCESS, and FAILURE keys.

test('request type is set correctly', () => {
  const dummyTypes = {
    REQUEST: 'FETCH_SINGLE_CHARACTER_REQUEST',
    SUCCESS: 'FETCH_SINGLE_CHARACTER_SUCCESS',
    FAILURE: 'FETCH_SINGLE_CHARACTER_FAILURE'
  };
  const newRequestType = createRequestTypes("FETCH_SINGLE_CHARACTER");
  expect(newRequestType).toEqual(dummyTypes);
});

Testing Redux Reducers

For reducers, we can test that when certain actions are fired off, that the state is updated correctly. To initialize state, redux fires off a default action called @@INIT. We can compare that against the initial state in our reducer to make sure it's equivalent.

test('initial state should be set correctly', () => {
  const state = characterReducer(undefined, { type: '@@INIT' });
  expect(state).toEqual(INITIAL_STATE);
});

Mocks and Spies

Mocking functions - or 'spying' on them - let's us test the inner workings of a function and not just the output. These mock functions can be used to test both custom and lifecycle methods on our React classes.

const mock = jest.fn();
Date.now = jest.fn(() => 1482363367071);

Jest Features

Besides your start and build commands, here are some useful ones to keep in your package.json:

"test": "jest", // run the test suite
"test:watch": "jest --watch", // re-runs test suite when a file changes
"test:coverage": "jest --coverage", // coverage report

Adding the --coverage flag will automatically set up a code coverage report. You can set configuration options for goal level of coverage and any files or folders to ignore in your package.json.

// example
"jest": {
    "setupFiles": [
      "./test-config.js"
    ],
    "coverageThreshold": {
      "global": {
        "branches": 50,
        "functions": 50,
        "lines": 50,
        "statements": 50
      },
      "./dev/**/*.js": {
        "branches": 40,
        "statements": 40,
        "line": 40
      },
      "./dev/components/**/*.js": {
        "branches": 40,
        "functions": 50
      }
    }
}

Module 12: Redux Forms

Setup

Redux Form is a higher order component that connects forms and form fields to the redux store. It has a lot of functionality to make managing form state easier and more straightforward.

npm install --save redux-form

The first step is to add redux-form to the rest of your reducers to be combined.

import { reducer as formReducer } from 'redux-form';
const reducers = {
  // your other reducers here
  form: formReducer
};

Now any forms you connect to redux-form will be accessible on state.form as long as the form component is wrapped with the reduxForm decorator. As an example, let's make the character fields editable in the /characters/:id route.

class CharacterDetails extends Component {
    // return the form to be connected
}

// Decorate the form component called ContactForm:
CharacterDetails = reduxForm({
  form: 'characterForm' // a unique name for this form
})(CharacterDetails);

export default characterDetails;

Now that the form is wrapped in the reduxForm, the individual input fields should be replaced with the Field component from redux-form.

return (
<form onSubmit={handleSubmit}>
    <Field
        name="gender"
        component="input"
    />
    <Field
        name="culture"
        component="input"
    />
    <Field
        name="born"
        component="input"
    />
    <Field
        name="died"
        component="input"
    />
    <FieldArray
        name="aliases"
        component="input"
    />
    <Field
        name="titles"
        component="input"
    />
    <Field
        name="portrayed-by"
        component="input"
    />
    <button className="btn btn-primary">Save</button>
</form>
);

That's all there is to the setup! You can now see the controlled inputs being updated on the redux state.

What about populating the form. Right now the inputs are blank but we want them to start already populated with the characters information. Redux Form gives us and way to set initial values for input fields but we have to connect it to the redux store with the connect() function. This method gives us access to state and props to get the initial data that we need.

CharacterDetailsForm = connect(
  (state, props) => {

    const {
        name,
        gender
    } = state.characters.character;

    const initialValues = {
        name: name || "Unknown",
        gender
    };

    return {
      initialValues
    };
  }
)(CharacterDetailsForm);

Now the name and gender will be populated when the form renders. Likewise the rest of the fields can be connected in the same manner - only the key in the initialValues object has to match up to the name field on the input.

There are many more advanced useful feature like form arrays and custom inputs that we will play around with later. The only left for now is to submit the form. Redux Form gives a handleSubmit function to the component that can be accessed on props. We can call that just like any other onSubmit function.

const { handleSubmit } = this.props;

Redux Form Custom Inputs

It would be nice to also have custom input fields to handle styling and display errors. Instead of setting component="input", we can just add a custom component like so, component={renderInput}. Then build the renderInput component:

const renderInput = ({ input, value, title, placeholder, label,
      className, meta: { error, touched }, type }) => (
    <div>
        <dt>{title}</dt>
        <dd>
            <input
                type={type || "text"}
                className={className} 
                placeholder={placeholder}
                {...input}
            />
            {error && <div className="alert alert-danger">{error}</div>}
        </dd>
    </div>
);

Redux Form handles passing the meta props to the component so we can handle cases if the component was touched or has a validation error (more on validation later). We can just add the custom input to the form and it's done:

<Field
    title="Gender"
    name="gender"
    component={renderInput}
/>

Redux Form Arrays

The form display fine except some of our components are arrays and not just single fields. For example, a character can have multiple aliases. Redux Form gives us another component to handle these cases (which might be the most useful feature in the library).

import { FieldArray } from 'redux-form';

Then the Field component can just be changed to a FieldArray one.

<FieldArray
    title="Aliases"
    name="aliases"
    component={renderAliasArray}
/>

Of course, there will need to be another custom component to handle the aliases array.

const renderAliasArray = ({ fields, title, meta: { error, dirty } }) => (
    <div>
        {fields.map((alias, i) =>
            <Field
                key={i}
                title={title}
                name={alias}
                type="text"
                component={renderInput}
                icon={
                    <FontAwesome
                        name='times'
                        onClick={() => fields.remove(i)}
                    />
                }
            />
        )}
        {error && dirty && <span className="alert alert-danger">{error}</span>}

        <button type="button" onClick={() => fields.push()}>Add Alias</button>
    </div>
)

Redux forms automatically gives us methods to add fields onto the array and remove them; without the headache! IMO this is the most useful feature of this library.

Redux Form Validation

Validation is also simple with Redux Forms. Just pass in a validate function to the reduxForm decorator:

const validate = (values) => {
  const errors = {};

  if (!values.gender) {
    errors.gender = 'Required';
  }
  if (values.aliases && values.aliases.length > 10) {
    errors.aliases = { _error: 'To many aliases. Limit to 10.' }
  }

  return errors;
};

CharacterDetails = reduxForm({
    form: 'characterForm',
    validate
  })(CharacterDetails);

Redux Forms handles passing in the field values as an argument, so we can test and validate however we want. Then, it's expecting us to return an error object with any of the 'invalid' fields as properties on that object with the messages that we set. Redux Forms handles the passing of the data to our Field components as well and we can display them however we want.

{error && <span className="alert alert-danger">{error}</span>}

Appendix A: Resources


Appendix B: Tutorials


Appendix C: Create React App

Create React App is a tool made by Facebook in response to complaints of how complicated it was to get started building applications with React. It works by globally installing npm install -g create-react-app and then running the create-react-app my-app command. This will take care of all tooling and setup so you can start building React Components right away.

create-react-app can a good solution for testing and learning purposes but there are some considerations to be made if you want to build production applications with this tool. It makes quite a few assumptions about the structure and needs of your application - although you can run an eject command to be able to configure the tooling as you want.

For more information, check out the github repo.

Appendix C: Type Checking with Prop Types

Type checking in React can be accomplished through a helper library called prop-types. This library allows you to check the types of data flowing through your application similar typescript or flow.

npm install --save prop-types

The standard pattern for typechecking with prop types is to place the types at the end of the component right before the export statement.

import PropTypes from 'prop-types';

const App = ({ style, title, child, onSave, names, error }) => {
    // component...
}

App.propTypes = {
    style: PropTypes.object,
    onSave: PropTypes.func,
    names: PropTypes.array,
    title: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.object,
    ]),
    child: PropTypes.node,
    error: PropTypes.any
};

export default App;

We can mandate the use of typechecking - or simply use a warning - in our eslint file:

"react/forbid-prop-types": "warn", // "error" or "off"
"react/no-unused-prop-types": "warn", // "error" or "off"

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published