Development environment

  1. Install Node. Use nvm or nvm-windows(https://github.com/coryhouse/pluralsight-redux-starter/archive/master.zip) if multiple versions are required.
  2. Jump into the project root, and install node packages npm install
  3. Install Chrome devtools for React developer tools and Redux Dev Tools.

Useful vscode extensions:

  • prettier - code formatting (enable the format on save setting)
  • ESLint - get red swigglys under source code

Boilerplate

So many options to bootstrap a new react project. Setting everything up manually is best, as it builds intuition about the pieces of infrastructure.

Some useful features in a JavaScript development environment that I’ll configure:

  • linting
  • code formatting
  • bundling and packing
  • ES6+ support
  • local web server

Webpack

Webpack for bundling grunt work and the local http server:

Webpack can transform front-end assets like HTML, CSS, and images if the corresponding loaders are included. webpack takes modules with dependencies and generates static assets representing those modules.

In the project root webpack.config.dev.js:

// node lacks support for ESM imports yet, stick with commonjs imports
const webpack = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

process.env.NODE_ENV = "development";

module.exports = {
  mode: "development",
  target: "web", //web = browser, node = backend
  devtool: "cheap-module-source-map", // source maps expose the original un-transpiled code for debugging
  entry: "./src/index", // app entry point (can omit extension)
  output: {
    // in development mode webpack keeps everything in-memory
    path: path.resolve(__dirname, "build"), // the route to serve the in-memory assets
    publicPath: "/", // the URI to serve in-memory assets to browser
    filename: "bundle.js", // the fake name that can be used to reference the in-memory bundle
  },
  devServer: {
    stats: "minimal", // reduce verbosity of stdout logs
    overlay: true, // overlay any errors that occur in the browser,
    historyApiFallback: true, // route all requests through index.html, to do deep linking with react-router
    disableHostCheck: true, //HACK: chrome bug
    headers: { "Access-Control-Allow-Origin": "*" }, //HACK: chrome bug
    https: false, //HACK: chrome bug
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html",
      favicon: "src/favicon.ico",
    }),
  ],
  module: {
    // what files should webpack handle?
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ["babel-loader"], // run babel on all js/jsx files before bundling
      },
      {
        test: /(\.css)$/,
        use: ["style-loader", "css-loader"], // webpack will bundle any css imported by js
      },
    ],
  },
};

This inline comments should explain most things, but one to draw out is cheap-module-source-map which exposes the original code, when developing and debugging. Try throwing the debugger keyword in some js, and refresh with chrome devtools open. The original source code will appear, with a message in the toolbar source mapped from bundle.js. Cool!

Babel

Babel for transpiling modern ES6 to ES5 for wider browser support:

Babel is a free and open-source JavaScript transcompiler that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript that can be run by older JavaScript engines. Babel is a popular tool for using the newest features of JavaScript.

Babel can be configured via .babelrc or package.json (under the key of babel):

"babel": {
  "presets": [
    "babel-preset-react-app"
  ]
}

The babel-preset-react-app preset, will transpile JSX and modern ES features such as object spreading, class props, dynamic imports and more.

NPM scripts

A general purpose task runner, available as the scripts key in package.json:

"scripts": {
  "start": "webpack-dev-server --config webpack.config.dev.js --port 3000"
},

Running npm start on the CLI will launch this code.

ESLint

Linters are useful for provided fast feedback of potential issues at development time, speeding up the dev/deploy/test cycle. ESLint is a popular choice for JavaScript and JSX:

A static code analysis tool for identifying problematic patterns found in JavaScript code.

Like babel, can be configured in a standalone file, but also supports being configured via package.json under the eslintConfig key:

"eslintConfig": {
    "extends": [
      "eslint:recommended",
      "plugin:react/recommended",
      "plugin:import/errors",
      "plugin:import/warnings"
    ],
    "parser": "babel-eslint",
    "parserOptions": {
      "ecmaVersion": 2018,
      "sourceType": "module",
      "ecmaFeatures": {
        "jsx": true
      }
    },
    "env": {
      "browser": true,
      "node": true,
      "es6": true,
      "jest": true
    },
    "rules": {
      "no-debugger": "off",
      "no-console": "off",
      "no-unused-vars": "warn",
      "react/prop-types": "warn"
    },
    "settings": {
      "react": {
        "version": "detect"
      }
    },
    "root": true
  }

Some notable features:

  • Plug-in around react best practices.
  • The babel-eslint parser provides strong compatibility with babel generated JavaScript, including support for ES2018, ESM (EcmaScript Modules) and JSX.
  • env tells ESLint to ignore use of global variables for well known packages that you intend to use.
  • rules relaxes the strictness around useful things like console.log and debugger
  • root makes this the base config, overriding settings in any user local ESLint configs.

The ESLint CLI can be manually run, however, getting the webpack watcher to automatically run ESLint is very convenient:

module: {
  rules: [
    {
      test: /\.(js|jsx)$/,
      exclude: /node_modules/,
      use: ["babel-loader", "eslint-loader"], // run babel on all js/jsx files before bundling
    }
  ]
}

Test things out by trying (for example) to add a global variable somewhere, as soon as the file is saved:

Module Error (from ./node_modules/eslint-loader/index.js):

/home/ben/code/react-playground/boilerplate/src/index.js
  4:1  error  'myGlobal' is not defined  no-undef

✖ 1 problem (1 error, 0 warnings)

Production dependencies

Dependency Use
bootstrap CSS Framework
immer Helper for working with immutable data
prop-types Declare types for props passed into React components
react React library
react-dom React library for DOM rendering
react-redux Connects React components to Redux
react-router-dom React library for routing
react-toastify Display messages to the user
redux Library for unidirectional data flows
redux-thunk Async redux library
reselect Memoize selectors for performance

Development dependencies

Dependency Use
@babel/core Transpiles modern JavaScript so it runs cross-browser
babel-eslint Lint modern JavaScript via ESLint
babel-loader Add Babel support to Webpack
babel-preset-react-app Babel preset for working in React. Used by create-react-app too.
css-loader Read CSS files via Webpack
cssnano Minify CSS
enzyme Simplified JavaScript Testing utilities for React
enzyme-adapter-react-16 Configure Enzyme to work with React 16
eslint Lints JavaScript
eslint-loader Run ESLint via Webpack
eslint-plugin-import Advanced linting of ES6 imports
eslint-plugin-react Adds additional React-related rules to ESLint
fetch-mock Mock fetch calls
html-webpack-plugin Generate HTML file via webpack
http-server Lightweight HTTP server to serve the production build locally
jest Automated testing framework
json-server Quickly create mock API that simulates create, update, delete
mini-css-extract-plugin Extract imported CSS to a separate file via Webpack
node-fetch Make HTTP calls via fetch using Node - Used by fetch-mock
npm-run-all Display results of multiple commands on single command line
postcss-loader Post-process CSS via Webpack
react-test-renderer Render React components for testing
react-testing-library Test React components
redux-immutable-state-invariant Warn when Redux state is mutated
redux-mock-store Mock Redux store for testing
rimraf Delete files and folders
style-loader Insert imported CSS into app via Webpack
webpack Bundler with plugin ecosystem and integrated dev server
webpack-bundle-analyzer Generate report of what’s in the app’s production bundle
webpack-cli Run Webpack via the command line
webpack-dev-server Serve app via Webpack

JSX

In React, everything is just Javascript. JSX takes this to the next level, providing syntactic sugar to crunch out lots of React.createElement, based on a HTML-esk syntax. babel.js takes care of the transpilation from HTML looking tags to actual Javascript. To get a real sense of this, go to babeljs.io/repl and type in const element = <h1>hello world</h1>;. You’ll see this get translated to:

"use strict";
var element = React.createElement("h1", null, "hello world");

Components all teh things

Within the ./src/components dir, create new jsx file to represent the component.

import React, { Component } from "react";

class Counter extends Component {
  //state = {}
  render() {
    return (
      <React.Fragment>
        <h1>hello world</h1>
        <button>Increment</button>
      </React.Fragment>
    );
  }
}

export default Counter;

DOM control

JSX expressions must have a single parent element, because babel needs a single React.createElement parent, before populating it with many child elements. Using a single outer <div would be one option, but if you really don’t want to include any outer DOM, can use the React.Fragment shown above.

Styling

Because JSX needs to boil down into JS, reserved JS keywords like class cannot be used in JSX. To set the class of an element, must use className.

<React.Fragment>
  <img src={this.state.imageUrl} alt="" />
  <span className="">{this.formatCount()}</span>
  <button>Increment</button>
</React.Fragment>

While using CSS classes is best, inline styles can be acheived by setting the style attribute on a JSX element to a Javascript object, like so:

styles = {
  fontSize: 12,
  fontWeight: "bold"
};

render() {
  return (
    <React.Fragment>
      <span style={this.styles} className="badge badge-primary m-2">
        {this.formatCount()}
      </span>

Or using an anonymous object like this:

<span style={{ fontSize: 30 }} className="badge badge-primary m-2">

Rendering Lists

The map (higher order) function, can be used to deal with lists:

class Counter extends Component {
  state = {
    count: 0,
    tags: ["tag1", "tag2", "tag3"]
  };

  render() {
    return (
      <React.Fragment>
        <ul>
          {this.state.tags.map(tag => (
            <li>{tag}</li>
          ))}
        </ul>

This will render a list of naked li, however React will throw a warning in the console:

Warning: Each child in a list should have a unique “key” prop.

In order to do virtual DOM to DOM comparison, React needs unique identifiers on everything. As there is no id on these li’s, the key attribute can be used:

<li key={tag}>{tag}</li>

Handling Events

JSX provides event handler attributes such as onClick:

handleIncrement() {
  console.log("Increment clicked", this.state.count);
}

render() {
  return (
    <React.Fragment>
      <button
        onClick={this.handleIncrement}
        className="btn btn-secondary btn-sm"
      >
        Increment
      </button>

State Management

In the above click handler, the console.log fails with:

TypeError: this is undefined

this in JS is context dependent. If a method is called on an object obj.method() then this is a reference to that object. However if a function is called statically i.e. function(); then this is a reference to the window object, or in strict mode undefined.

React rule of thumb:

The component that owns some state, should be responsible for mutating it.

Option 1: Binding

In the constructor of the component, after calling super() can bind this to the static function like so:

constructor() {
  super();
  this.handleIncrement = this.handleIncrement.bind(this);
}

this is now available to the event handler function.

Option 2: Arrows

Just use an arrow function:

handleIncrement = () => {
  console.log("Increment clicked", this.state.count);
};

Now that the event handler has this access, cannot just start mutating it, for example this.state.count++ will not affect the UI, as React is not aware that this custom piece of state has changed, and what is impacted by the state change.

To change state, React provides setState:

handleIncrement = () => {
  this.setState({ count: this.state.count + 1 });
};

Passing Parameters

It is common to want to pass some state along to the event handler, when its invoked.

One option is to create a wrapper function:

handleIncrement = product => {
  console.log(product);
  this.setState({ count: this.state.count + 1 });
};

doHandleIncrement = () => {
  this.handleIncrement({ id: 1 });
};

render() {
  return (
    <React.Fragment>
      <button
        onClick={this.doHandleIncrement}
        className="btn btn-secondary btn-sm"
      >
        Increment
      </button>

For consiseness wrapper function can be represented as an inline (arrow) function:

<button
  onClick={() => this.handleIncrement({ id: 1 })}
  className="btn btn-secondary btn-sm"
>
  Increment
</button>

Component Composition

A React app is a tree of components. In index.js is kicked off with the:

ReactDOM.render(<Counter />, document.getElementById("root"));

To compose many Counter components, one option is to create an outer Counters component:

import React, { Component } from "react";
import Counter from "./counter";

class Counters extends Component {
  state = {
    counters: [
      { id: 1, value: 0 },
      { id: 2, value: 0 },
      { id: 3, value: 0 },
      { id: 4, value: 0 },
    ],
  };

  render() {
    return (
      <div>
        {this.state.counters.map((c) => (
          <Counter key={c.id} />
        ))}
      </div>
    );
  }
}

export default Counters;

Passing Data

While the above Counters component successfully composes several Counter’s from a list, it does not pass state to the child components.

State can be propagated to child components, as attribute like so:

<Counter key={c.id} value={c.value} selected={true} />

This state is exposed to the target components as props. A console log in child component Counter render method:

render() {
  console.log("props", this.props);

Results in:

props Object { value: 0, selected: true, … }
props Object { value: 0, selected: true, … }
props Object { value: 3, selected: true, … }
props Object { value: 0, selected: true, … }

props can be used throughout the component, and even directly in the component state, like so:

class Counter extends Component {
  state = {
    count: this.props.value,
    tags: ["tag1", "tag2", "tag3"],
    imageUrl: "https://picsum.photos/200",
    address: {
      street: ""
    }
  };

Passing Children (passing down JSX)

Previously the Counters component, composed many Counter instances, like so, while defining a few props:

<div>
  {this.state.counters.map(c => (
    <Counter key={c.id} value={c.value} selected={true} />
  ))}
</div>

To make components even more useful, its possible to pass in inner JSX content, for example:

<Counter key={c.id} value={c.value} selected={true}>
  <h2>semaphore</h2>
</Counter>

The props of the child component, exposes this h2 via a list called children:

props {…}
        children: {…}
          "$$typeof": Symbol(react.element)
          _owner: Object { tag: 1, key: null, index: 0, … }
          _self: Object { props: {}, context: {}, refs: {}, … }
          _source: Object { fileName: "./src/components/counters.jsx", lineNumber: 19 }
          _store: Object { … }
          key: null
          props: Object { children: "semaphore" }
          ref: null
          type: "h2"
          <prototype>: Object { … }
        key:
        selected: true
        value: 0
        <get key()>: function warnAboutAccessingKey()
        <prototype>: {…

To make use of the props.children property is easy:

render() {
  return (
    <React.Fragment>
      {this.props.children}
      <span className={this.getBadgeClasses()}>{this.formatCount()}</span>

This will render the children as defined in the calling component, in this case an h2 tag will be emitted.

Interestingly props is immutable, and can’t be modified, state should be used.

Three Rules about State

Rule one, never modify state directly, always use setState().

Rule two, state updates are asynchronous, as a result the correct values of this.props and this.state at the time they are queried, can’t always be guaranteed. The second form of setState() that accepts a function (not the usual object) should be used.

this.setState((state, props) => ({
	counter: state.counter + props.increment
}));

Rule three, state updates are magically merged. State might contain several variables:

constructor(props) {
  super(props);
  this.state = {
    posts: [],
    comments: []
  };
}

Individual variables can be updated independently, with separate setState() calls, without clobbering.

componentDidMount() {
  fetchPosts().then(response => {
    this.setState({
      posts: response.posts
    });
  });

  fetchComments().then(response => {
    this.setState({
      comments: response.comments
    });
  });
}

Raising and Handling Events between Components

State management rule of thumb:

The component that owns some state, should be responsible for mutating it.

A common scenario is for parent or outer components to define the state that is consumed by downstream components. Given the rule of thumb above, how should a downstream component go about modifying the original state?

One good option is the observer pattern, in which a child component raises an event, which can be handled by its parent component to react and respond to a state change.

For example, the Counters component defines several Counter components. A component can elect to delete itself by displaying a delete button to the user. If clicked, the Counter instance could raise an onDelete event. Counters could handle this event with handleDelete().

In the Counters component:

  • create event handler e.g. handleDelete responsible for mutating the original state that relates to the event. important in React, you can’t just mutate state and expect React to know about it, instead you must reassign the state using Reacts setState function (see below)

  • pass this handler function downstream to Counter components as props

    class Counters extends Component { state = { counters: [ { id: 1, value: 0 }, { id: 2, value: 0 }, { id: 3, value: 3 }, { id: 4, value: 2 } ] };

    handleDelete = counterId => {
      console.log("Event handler called", counterId);
      const counters = this.state.counters.filter(c => c.id !== counterId);
      this.setState({ counters: counters });
    };
    
    render() {
      return (
        <div>
          {this.state.counters.map(c => (
            <Counter
              key={c.id}
              value={c.value}
              onDelete={this.handleDelete}
              selected={true}
            />
          ))}
        </div>
    

Using the React Developer Tools browser extension, can drill down into Counters to an individual Counter instance, and check out its Props (right hand panel) - note the onDelete prop:

Props
  onDelete: handleDelete()
  selected: true
  value: 0

In the Counter component, simply need to wire in the onDelete now available in props to the onClick:

<button
  onClick={() => this.props.onDelete(this.props.id)}
  className="btn btn-danger btn-sm m-2"
>

Passing Object props

Over time, the number of individual props defined may continue to swell, like so:

<Counter
  key={c.id}
  value={c.value}
  onDelete={this.handleDelete}
  selected={true}
/>

Instead, of mapping each field as a prop, why not pass the entire object (in this case called counter) along:

<Counter
  key={counter.id}
  counter={counter}
/>

Make sure to update affected props reference in the child components:

class Counter extends Component {
  state = {
    count: this.props.counter.value,
    ...

Controlled Components

Local state within components, can cause components to get out of wack. For example, when setting the initial state of a child component from props only happens when the component is created. Future changes to state in the parent, that was passed down as props to its children, will not flow through after initial creation.

A controlled component is one that:

  • Receives essential data needed to render via props from its parent.
  • Never manages this as state locally, but instead fires events to request the data be updated back in the parent.

Refactoring steps:

  • Remove any local state
  • Any references to this.state should be updated to use props directly, and trigger events to communicate with the parent (observer) where needed.

For example, this button now delegates its click handler to a func passed through by its housing (parent) component:

<button
  onClick={() => this.props.onIncrement(this.props.counter)}
  className="btn btn-secondary btn-sm"
>

Synchronising Components

Sometimes, as the architecture of the view changes, new components get introduced. This can modify the shape of the component tree.

For example, introducing a navbar component that requires access to the state of another unrelated component.

The general pattern for this is known as lifting the state upwards. The higher in the tree the state lives, the more widely it can be propagated through other downstream components.

The Counters component used to be responsible for managing the counters in its own state, rendering each child Counter component. Lifting this state upwards to top level App component, means refactoring this.state access with this.props accessors. Event handlers must bubble upwards too:

<Counter
  key={c.id}
  counter={c}
  onDelete={this.props.onDelete}
  onIncrement={this.props.onIncrement}
  selected={true}

In the top level App component, the render can now propagate state as needed, for example only passing the totalCounters to the NavBar component, while passing down the full counter objects to Counters:

render() {
  return (
    <React.Fragment>
      <NavBar
        totalCounters={this.state.counters.filter(c => c.value > 0).length}
      />
      <main className="container">
        <Counters
          counters={this.state.counters}
          onReset={this.handleReset}
          onDelete={this.handleDelete}
          onIncrement={this.handleIncrement}
        />
      </main>
    </React.Fragment>
  );
}

Stateless Functional Components

When a component is simple enough to contain only a render. No supporting funcs or logic. No object state (i.e. props only access to data).

The following NavBar component is a prime candidate for conversion:

class NavBar extends Component {
  render() {
    return (
      <nav className="navbar navbar-light bg-light">
        <a className="navbar-brand" href="#">
          Cool React App{" "}
          <span className="badge badge-pill badge-secondary">
            {this.props.totalCounters}
          </span>
        </a>
      </nav>
    );
  }
}

Converting a class based component to functional, involves simply defining the component as an arrow function that returns a JSX element. Any use of object this state such as this.props, need to be defined as parameters on the arrow function (props will be dependency injected automatically).

const NavBar = (props) => {
  return (
    <nav className="navbar navbar-light bg-light">
      <a className="navbar-brand" href="#">
        Cool React App{" "}
        <span className="badge badge-pill badge-secondary">
          {props.totalCounters}
        </span>
      </a>
    </nav>
  );
};

Destructuring Data (Arguments)

Reacts component model and one way binding means that data and callbacks get passed around allot, as data (props) is propagated down the component hierarchy.

const NavBar = (props) => {
  return (
    <nav className="navbar navbar-light bg-light">
      <a className="navbar-brand" href="#">
        Cool React App{" "}
        <span className="badge badge-pill badge-secondary">
          {props.totalCounters}
        </span>

To simplify the constant need to prefix props., can leverage ES6 argument destructuring to tear apart the individual data fields.

const NavBar = ({totalCounters}) => {
  return (
    <nav className="navbar navbar-light bg-light">
      <a className="navbar-brand" href="#">
        Cool React App{" "}
        <span className="badge badge-pill badge-secondary">
          {totalCounters}
        </span>

Which is equivalent to:

const NavBar = (props) => {
  const { totalCounters } = props;
  return (
    ...

Functional components

const NavBar = (props) => { becomes const NavBar = ({ totalCounters }) => {

Resulting in cleaner prop access:

const NavBar = ({ totalCounters }) => {
  return (
    <nav className="navbar navbar-light bg-light">
      <a className="navbar-brand" href="#">
        Cool React App{" "}
        <span className="badge badge-pill badge-secondary">
          {totalCounters}
        </span>
      </a>
    </nav>
  );
};

Class components

Involves ripping apart the props at the top of the render:

class Counters extends Component {
  render() {
    return (
      <div>
        <button
          onClick={this.props.onReset}
          className="btn btn-primary btn-sm m-2"
        >
          Reset
        </button>
        {this.props.counters.map((c) => (
          <Counter
            key={c.id}
            counter={c}
            onDelete={this.props.onDelete}
            onIncrement={this.props.onIncrement}
            selected={true}
          />
        ))}
      </div>
    );
  }
}

After:

class Counters extends Component {
  render() {
    const { onReset, counters, onDelete, onIncrement } = this.props;

    return (
      <div>
        <button onClick={onReset} className="btn btn-primary btn-sm m-2">
          Reset
        </button>
        {counters.map((c) => (
          <Counter
            key={c.id}
            counter={c}
            onDelete={onDelete}
            onIncrement={onIncrement}
            selected={true}
          />
        ))}
      </div>
    );
  }
}

Life cycle Hooks

Allow activities to be performed when interesting events occur. Some interesting life cycle events available:

Phase 1 the birth phase (mount)

When an instance of a component is first created and injected in the DOM. Available hooks in this phase include:

  • constructor component constructor
  • render the component itself, and all its children
  • componentDidMount after component output is rendered into DOM, perfect doing AJAX laundry or setup of things like timers (setInterval)

The constructor can access props only if its defined as parameter in constructor (and DI injected).

constructor() {
  super();
  console.log('App - Constructor', this.props); //'App - Constructor > undefined'
}

Dependency injected props:

constructor(props) {
  super(props);
  console.log('App - Constructor', this.props); //'App - Constructor > Object {  }'
}

At constructor time, setState is not usable. This is the only time it is acceptable to set this.state directly, for example:

this.state = {
  posts: [],
  comments: []
}

Phase 2 the update phase

When the state or props of a component are modified. Available hooks here:

  • render
  • componentDidUpdate called after a component is updated, which means there is some new state or props. The previous version of the state or props can be compared to the latest state or props, and if necessary necessary work such as firing off new ajax fetches, and so on (this is a nice optimisation technique).

componentDidUpdate provides the prevProps and prevState values, which is useful to detect is a piece of data has changed that is worthy of other work (such as a fresh ajax call to the server):

componentDidUpdate(prevProps, prevState) {
  console.log('prevProps', prevProps);
  console.log('prevState', prevState);

  if (prevProps.counter.value !== this.props.counter.value) {
    // ajax fetch new data from server
  }
}

Phase 3 the death phase (unmount)

When a component life comes to an end (e.g. the removal of a Counter component).

  • componentWillUnmount fired as soon as a component is removed from the DOM, perfect for tearing down resources such as timers or listeners.

Effects (hooks)

Effects were introduced in early 2019, and give function components the same degree of hooks as class components.

Class components are no longer necessary, but are still supported.

A class component (before):

class ManageCoursesPage extends React.Component {
  componentDidMount() {
    const { courses, authors, loadCourses, loadAuthors } = this.props;

    if (courses.length === 0) {
      loadCourses().catch((error) => {
        alert("Loading courses failed " + error);
      });
    }

    if (authors.length === 0) {
      loadAuthors().catch((error) => {
        alert("Loading authors failed " + error);
      });
    }
  }

  render() {
    return (
      <>
        <h2>Manage Course</h2>
      </>
    );
  }
}

Converted to a functional component with effects (after):

import React, { useEffect } from "react";
...
function ManageCoursesPage({ courses, authors, loadCourses, loadAuthors }) {
  useEffect(() => {
    if (courses.length === 0) {
      loadCourses().catch((error) => {
        alert("Loading courses failed " + error);
      });
    }

    if (authors.length === 0) {
      loadAuthors().catch((error) => {
        alert("Loading authors failed " + error);
      });
    }
  }, []);
  // The 2nd param is list of items to monitor, if they change, fire the effect.
  // An empty array causes the effect to only fire once (i.e. like componentDidMount)

  return (
    <>
      <h2>Manage Course</h2>
    </>
  );
}

Some highlights:

  • The props destructuring happens right in the function signature.
  • componentDidMount is replaced with useEffect
  • No need for an explicit render, as its implied in a functional component

Debugging

Get the React Developer Tools Chrome or FF extension.

Using the newly provided React tab within dev tools, will now be able to clearly view the tree hierarchy of React components.

Component in this view are selectable, and will be assigned to variable $r, like so:

<Counters>
  <div>
    <Counter key="1" value={0} selected={true}>...</Counter> == $r
    <Counter key="2" value={0} selected={true}>...</Counter>
    <Counter key="3" value={3} selected={true}>...</Counter>
    <Counter key="4" value={2} selected={true}>...</Counter>
  </div>
</Counters>

In the same way selecting a piece of DOM with the Elements tab would assign it to $0. Using the $r reference in the Console can begin to inspect and play with it:

  • call its render()

React Router

SPA web frameworks render components based on some state, making them appear like multiple pages. This is not the case.

React Router adds the notion of conditionally rendering components based on the route specified in the URI, such as / for home, /about for the about page, and so on.

Benefits of faking routes and pages in a SPA:

  • aids crawlers (SEO) to walk the site hierarchy,
  • bookmarkable URL’s,
  • reduces the initial design overhead of planning out the state/props to facilitate component rendering (i.e. can lean on the router)

React Router makes this possible by provides a set of navigation components:

  • BrowserRouter

  • HashRouter

  • Link provides declarative navigation around your application. Note the use of backticks for setting the to prop with actual state:

Install the package:

$ yarn install react-router-dom

Import to the (top level) component responsible for rendering the content portion of the app (e.g. App.js):

import { BrowserRouter } from 'react-router-dom'

Or optionally alias it to the slicker Router:

import { BrowserRouter as Router } from 'react-router-dom'

Wrap everything in the render() with the BrowserRouter component:

render() {
  return (
    <BrowserRouter>
      <NavBar
        totalCounters={this.state.counters.map(a => a.value).reduce((accumulator, currentValue) => accumulator + currentValue)}
      />

      <div className="d-flex" id="wrapper">
        <SideBar />

        <div id="page-content-wrapper">
          <div className="container-fluid">
            <Counters
              counters={this.state.counters}
              onReset={this.handleReset}
              onDelete={this.handleDelete}
              onIncrement={this.handleIncrement}
            />
          </div>
        </div>
      </div>
    </BrowserRouter>
  );
}

Roll out the Link component, to parts of the UI that should influence the routes (e.g. a sidebar or nav menu for changing pages). Here is my custom SideBar component:

render() {
  const { gists } = this.state;

  return (
    <div className="bg-light border-right" id="sidebar-wrapper">
      <div className="sidebar-heading">Router Fun</div>
      <div className="list-group list-group-flush">
        { gists ? (
              gists.filter(gist => gist.description !== '').map(gist => (
                <Link to={`/g/${gist.id}`} className="list-group-item list-group-item-action bg-light">
                  {gist.description}
                </Link>
              ))
            ) : (
              <div>Loading...</div>
            )}
      </div>
    </div>
  );
}

Now back in the top level component using the BrowserRouter component, register some Routes:

render() {
  return (
    <BrowserRouter>
      <React.Fragment>
        <div className="d-flex" id="wrapper">
          <SideBar gists={this.state.gists} />

          <div id="page-content-wrapper">
            <div className="container-fluid">
              <Route path="/g/:gistId" component={Gist}></Route>

Clicking a Link will change the URI to something like /g/77501744874a981e61c86ba3edfb4a46 (the gist id), the route will match, which will render the Gist component:

import React, { Component } from "react";

const Gist = ({ match }) => (
  <div className="container">
    <h1>{match.params.gistId}</h1>
  </div>
);

export default Gist;

I want a home page to render at /. Back in the BrowserRouter, register a new / Route:

<Route
  path="/"
  exact={true}
  render={(props) => (
    <Home
      {...props}
      counters={this.state.counters}
      onReset={this.handleReset}
      onDelete={this.handleDelete}
      onIncrement={this.handleIncrement}
    />
  )}
/>

This is a render route, unlike the previous component route that was used. As you can see, allows you to bind custom props, in addition to the 3 route props (preserved with the spread operator). Note, the exact prop here prevents this route from matching whenever there is a leading forward slash / (basically every URL). Exact, you guessed it, forces an exact match of a single /. Working this second home route into the existing BrowserRouter:

render() {
  return (
    <BrowserRouter>
      <React.Fragment>
        <div className="d-flex" id="wrapper">
          <SideBar gists={this.state.gists} />

          <div id="page-content-wrapper">
            <div className="container-fluid">

              <Route path="/" exact={true} render={(props) =>
                  <Home {...props}
                    counters={this.state.counters}
                    onReset={this.handleReset}
                    onDelete={this.handleDelete}
                    onIncrement={this.handleIncrement} />} />

              <Route path="/g/:gistId" component={Gist}></Route>

react-router also features a redirect component.

import { Redirect } from "react-router-dom";

class CoursesPage extends React.Component {
  state = {
    redirectToAddCoursePage: false,
  };

  render() {
    return (
      <>
        {this.state.redirectToAddCoursePage && <Redirect to="/course" />}
        <h2>Courses</h2>
        <button
          style={{ marginBottom: 20 }}
          className="btn btn-primary add-course"
          onClick={() => this.setState({ redirectToAddCoursePage: true })}
        >
          Add Course
        </button>

        <CourseList courses={this.props.courses} />
      </>
    );
  }
}

It’s also possible to use react-routers history to change the URL to a location. Any component routed by react-router will automatically be injected with a history prop.

Before:

function handleSave(event) {
  event.preventDefault();
  saveCourse(course);
}

After:

function handleSave(event) {
  event.preventDefault();
  saveCourse(course).then(() => {
    history.push("/courses");
  });
}

Testing

React by design is incredibly testable, something that was difficult in traditional server-side technologies. For example, its possible to render a single component against an in-memory DOM and fake click events, to test a component behaves as expected.

Like everything in the react ecosystem, there are lots of choices.

Popular testing frameworks include jest (facebook and bundled with CRA), mocha (very configurable with large ecosystem), jasmine (like mocha but slighly less configurable), tape (minimalist) and AVA.

Helper libraries exist to aid verification of components, such as mocking and shallow rendering (not rendering the child hierarchy). Popular options include ReactTestUtils, Enzyme and React testing library.

Jest

Setup an npm script runner for jest:

{
  "name": "ps-redux",
  "description": "React and Redux Pluralsight course by Cory House",
  "scripts": {
    "start": "run-p start:dev start:api",
    "start:dev": "webpack-dev-server --config webpack.config.dev.js --port 3000",
    "prestart:api": "node tools/createMockDb.js",
    "start:api": "node tools/apiServer.js",
    "test": "jest --watch"
  },
  "jest": {
    "setupFiles": [
      "./tools/testSetup.js"
    ],
    "moduleNameMapper": {
      "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/tools/fileMock.js",
      "\\.(css|less)$": "<rootDir>/tools/styleMock.js"
    }
  },

The fileMock.js and styleMock.js provide a testing hook for when files of a particular type get consumed by the rendering process.

Jest automatically searches for tests in files that end in .test.js or .spec.js, here’s a test in a file called index.test.js:

it("should pass", () => {
  expect(true).toEqual(true);
});

Snapshot testing with jest, captures the rendered output of components, and compares the output over time to ensure regressions havent slipped in.

A benefit of setting up mockdata with something like JSON Server is that the same mock data can be used in the unit tests.

import React from "react";
import CourseForm from "./CourseForm";
import renderer from "react-test-renderer";
import { courses, authors } from "../../../tools/mockData";

it("sets submit button label to 'Saving...' when saving prop is true", () => {
  const tree = renderer.create(
    <CourseForm
      course={courses[0]}
      authors={authors}
      onChange={jest.fn()}
      onSave={jest.fn()}
      saving={true}
    />
  );

  expect(tree).toMatchSnapshot();
});

it("sets submit button label to 'Save' when saving prop is false", () => {
  const tree = renderer.create(
    <CourseForm
      course={courses[0]}
      authors={authors}
      onChange={jest.fn()}
      onSave={jest.fn()}
      saving={false}
    />
  );

  expect(tree).toMatchSnapshot();
});

Running this will result in jest creating an __snapshots__ directory next to the test file, with the output of the rendered DOM.

jest.fn() creates an empty function, to save you the effort, so you can stub any function props.

Now with the snapshots captured, these could be committed into your git repo, and verified as part of your CI pipeline. If for example, the label of the save button changed or the logic basec on the saving prop broken the snapshot test would fail.

Jest in watch mode provides a bunch of options:

Watch Usage

  • Press a to run all tests.
  • Press f to run only failed tests.
  • Press p to filter by a filename regex pattern.
  • Press t to filter by a test name regex pattern.
  • Press q to quit watch mode.
  • Press u to update failing snapshots.
  • Press Enter to trigger a test run.

React Test Utils

Takes two approaches to component rendering; shallowRender and renderIntoDocument.

shallowRender will render only a single component (not child components) which is useful for asserting the coupling to other components and has no dependency on an actual DOM. Not having a DOM doesn’t work for all scenarios, particularly things like button click events.

renderIntoDocument will do a full component (and its children) render, and requires a DOM. Requiring a DOM doesn’t necessary mean a browser however, with headless DOM libs like jsdom.

jsdom is a pure-JavaScript implementation of many web standards, for use with Node.js

Some examples of its API are scryRenderedDOMComponentsWithClass(), findRenderedDOMComponentWithClass(), scryRenderedDOMComponentsWithTag(), findRenderedDOMComponentWithTag(). The scry variants finds all DOM elements of components in the rendered tree that match, while the find variants expects only a single result.

The Simulate function allows you to fake DOM events such as key pressing or clicking an element:

const node = this.button;
ReactTestUtils.Simulate.click(node);

Enzyme

An wrapper by airbnb that includes ReactTestUtils, JSDOM (in-memory DOM) and Cheerio (CSS selector support), provides a simplified, headless testing environment and API. For example, it provides a single find function that accepts CSS style selectors.

describe('<MyComponent />', () => {
  it('renders three <Foo /> components', () => {
    const wrapper = shallow(<MyComponent />);
    expect(wrapper.find(Foo)).to.have.lengthOf(3);
  });

Enzyme offers a few rending methods:

  • Shallow rendering: shallow(), no DOM, no child components, fast
  • Full DOM rendering: mount(), in-memory DOM with JSDOM, child components rendered, slow
  • Static rendering

To setup enzyme an adapter specific to the version of react you’re running needs to be bootstrapped ./tools/testSetup.js:

import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });

Configure jest to hook this in package.json:

  "jest": {
    "setupFiles": [
      "./tools/testSetup.js"
    ],

Create a file that ends in .test.js for jest to pick it up, putting enzyme in the name is a nice touch ex: CourseForm.Enzyme.test.js:

import React from "react";
import CourseForm from "./CourseForm";
import { shallow } from "enzyme";

function renderCourseForm(args) {
  const defaultProps = {
    authors: [],
    course: {},
    saving: false,
    errors: {},
    onSave: () => {},
    onChange: () => {},
  };

  const props = { ...defaultProps, ...args };
  return shallow(<CourseForm {...props} />);
}

it("renders form and header", () => {
  const wrapper = renderCourseForm();
  // console.log(wrapper.debug());
  expect(wrapper.find("form").length).toBe(1);
  expect(wrapper.find("h2").text()).toEqual("Add Course");
});

it('labels save buttons as "Save" when not saving', () => {
  const wrapper = renderCourseForm();
  expect(wrapper.find("button").text()).toBe("Save");
});

it('labels save button as "Saving..." when saving', () => {
  const wrapper = renderCourseForm({ saving: true });
  expect(wrapper.find("button").text()).toBe("Saving...");
});

The renderCourseForm() cuts down on props wrangling boilerplate code, that each test would need to setup.

Finally to contrast shallow versus full DOM rendering that enzyme supports:

import React from "react";
import Header from "./Header";
import { mount, shallow } from "enzyme";
import { MemoryRouter } from "react-router-dom";

// Note how with shallow render you search for the React component tag
it("contains 3 NavLinks via shallow", () => {
  const numLinks = shallow(<Header />).find("NavLink").length;
  expect(numLinks).toEqual(3);
});

// Note how with mount you search for the final rendered HTML since it generates the final DOM.
// We also need to pull in React Router's memoryRouter for testing since the Header expects to have React Router's props passed in.
it("contains 3 anchors via mount", () => {
  const numAnchors = mount(
    <MemoryRouter>
      <Header />
    </MemoryRouter>
  ).find("a").length;

  expect(numAnchors).toEqual(3);
});

Production

Its not unusual for the webpacked bundle.js to weigh in about 2MB.

The development toolchain that has been setup, serves the bundled outputs from memory. Recall that in webpack.config.dev.js:

module.exports = {
  mode: "development",
  target: "web",
  output: {
    // in development mode webpack keeps everything in-memory
    path: path.resolve(__dirname, "build"), // the route to serve the in-memory assets
    publicPath: "/", // the URI to serve in-memory assets to browser
    filename: "bundle.js", // the fake name that can be used to reference the in-memory bundle
  },
};

In production, want the webpack bundled assets (index.html, bundle.js, styles.css) dumped out to the /build folder.

Some goals for a production build:

  • lint and run test
  • bundle and minify js and css
  • create original js and css sourcemaps (to aid debugging)
  • shake out development related packages out of the dependency tree
  • build react in production mode
  • make a bundle report (highlighting dependencies etc)
  • deploy the build to a local webserver such as nginx

Redux store config

First the redux store currently has some convenient development related settings such as reduxImmutableStateInvariant which barfs if you attempt to mutate state. Its a good idea to split out development (configureStore.dev.js) and production (configureStore.prod.js) related store configs. In configureStore.js use CommonJS to dynamically import the appropriate setting based on NODE_ENV:

if (process.env.NODE_ENV === "production") {
  module.exports = require("./configureStore.prod");
} else {
  module.exports = require("./configureStore.dev");
}

Webpack

Create a webpack.config.prod.js in the root of project. Import in MiniCssExtractPlugin (CSS minifying) and webpackBundleAnalyzer (webpack analysis report).

Set process.env.NODE_ENV and mode to production.

Change devtool: "cheap-module-source-map" to devtool: "source-map"

Remove the entire devServer section.

Using the DefinePlugin ensure the process.env.NODE_ENV is defined throughout:

plugins: [
  new webpack.DefinePlugin({
    "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
    "process.env.API_URL": JSON.stringify("http://localhost:3001"),
  }),

Register the webpackBundleAnalyzer and MiniCssExtract plugins. Webpack will include a content hash in the CSS bundle name, so a new file will be served up as changes to the CSS occur.

plugins: [
  new webpackBundleAnalyzer.BundleAnalyzerPlugin({ analyzerMode: "static" }),
  new MiniCssExtractPlugin({
    filename: "[name].[contenthash].css",
  }),

Update the HtmlWebpackPlugin plugin which is responsible for generating the base index.html file and including the necessary includes into it, such as the bundle references. Enable minification of everything, including any html content.

Update the CSS handling to support minification by defining an optimization section. The OptimizeCSSAssetsPlugin uses cssnano under the hood, followed by css-loader to pull in any css references made throughout the js.

optimization: {
  minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
},
module: {
  // what files should webpack handle?
  rules: [
    {
      test: /(\.css)$/,
      use: [MiniCssExtractPlugin.loader, "css-loader"],
    },

Setup npm scripts

In package.json define some scripts, with pre and post hooks, to:

  1. run all unit tests (test:ci)
  2. clean the build directory (rimraf is a cross platform rm -rf)
  3. webpack based on production config (cleaning and running unit tests before, and spooling up the fake API and serving the freshly bundled files in a small web server http-server)
"scripts": {
  "start": "run-p start:dev start:api",
  "start:dev": "webpack-dev-server --config webpack.config.dev.js --port 3000",
  "prestart:api": "node tools/createMockDb.js",
  "start:api": "node tools/apiServer.js",
  "test": "jest --watch",
  "test:ci": "jest",
  "clean:build": "rimraf ./build && mkdir build",
  "prebuild": "run-p test:ci clean:build",
  "build": "webpack --config webpack.config.prod.js",
  "postbuild": "run-p start:api serve:build",
  "serve:build": "http-server ./build"
},

Troubleshooting

  1. Run npm install
  2. Don’t use symlinks, as it causes issues with file watches.
  3. Delete any .xeslintrc in your home directory and disable any ESLint plugin.
  4. On Windows? Run shell as an administrator.
  5. Ensure you do not have NODE_ENV=production in your env variables as it will not install the devDependencies.
  6. Nothing above work? Delete your node_modules folder and re-run npm install.