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.

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.

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 with 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>

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? Open your console 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.