Web Development March 24, 2018 1

Building a SPA with React, Babel, and Webpack


Introduction

This post is a detailed guide for setting up and using React 16 to build a single-page application. In addition to covering the basics of using React, this guide also covers configuring a development and build environment using Webpack and Babel.

React is one of the most popular JavaScript libraries for creating view components and single-page applications (SPAs). Part of the reason for React’s popularity is that the framework doesn’t try to be all things to all people. Compared to other frameworks such as Angular or View, React remains fully agnostic about any parts of a web project outside of the view layer. This gives React a lot of flexibility in how it can be used. It also means that even a simple web application will require additional frameworks and features, and React has little opinion on which of these to use. The limited scope of React also means that outside of a few automated tools, there is no one way to scaffold a React project, making the initial setup of a React app a little more complicated.

Many of the existing introductions and guides about React focus either on developing a React application entirely in a browser, or make use of some of the pre-built templates. While these approaches are great for just learning the React framework, they gloss over many of the important features that make React unique. As such, the primary purpose of this guide will be in teaching how to set up a fully featured environment for building React applications and components. Although we will be using additional libraries, no scaffolding utilities or code-generation tools will be used.

While React does not have an opinion on which additional frameworks and utilities to use, this guide does. However, the tools presented here are only one example of setting up a development environment and build-chain. The choices on tools and libraries were made with an eye towards both simplicity and common use. The goal is to keep the number of additional resources to a minimum, while still allowing for a full, production-viable build of a React application. Additionally, we will be using the newer language features of ES6 (and even some ES7) and the latest version React.


Table of contents

  1. Overview: Building a single page application
  2. Additional frameworks and technologies
  3. Installing dependencies with NPM
  4. Configuring Webpack and the development server
  5. Building a React component with JSX
  6. SPA: Structure and component selection
  7. SPA: Building the components
  8. SPA: Navigation and Routing
  9. Conclusion


Overview: Building a single-page application

Many modern websites function more like applications than traditional web pages. Some sites, such as Gmail are built entirely as an application, while others, like Facebook (the creators of React), incorporate elements of both. The process behind these web applications is often referred to a single page app or SPA. In a SPA, the website responds to user actions such as clicks by dynamically loading and unloading content, as opposed to navigating to or away from a page. In a SPA there any seldom any page reloads, and the user might never even see the address bar or URL change. Much like a desktop or mobile app, SPAs feature elements like menu bars and items, floating windows, animations, and other non-HTML components. SPAs are not all that new, but they have become increasingly complex to build.

Much of the complexity of a SPA comes from how a site’s look and feel must update when a user takes an action, or when the underlying data is changed. In a traditional website, the browser would simply navigate to a new page and all of the needed content would load or reload. But in a SPA, only some of the content changes. React aims to simplify this complexity but automatically managing how these visual changes occur, leaving the developer only responsible for specifying the underlying change itself. The data behind all of these changes is called state. When developing React components you are specifying how a component’s state will change, and then relying on React to update the view accordingly. State change = view change. In React, this auto-updating is in a single direction only. Changing state might result in a changed component view, but if a user were to change something in a view (say by entering a new quantity in a shopping cart), React will not automatically update the underlying state. This is one of the features that distinguishes React from Angular, which is another popular framework for creating views.

The SPA that we will be building will be visually and functionally simlar to to this example:

SPA example

You can find a live version here: Single Page App Demo. Take a minute or two to play around with the sample application. Notice that although it looks like a traditional webpage, when you click on the various links no page loads occur. You might notice that the URL in your browser updates, but perhaps not in the way you would expect. In fact, the URL in your browser doesn’t even point to an HTML file, just a directory with a hash symbol after it. Also, although no page reloads occur, you can still navigate as normal using your browser’s back and forward buttons. All of these are features common to most SPAs, and we will cover each in more detail.


Additional frameworks and technologies

When most people use React, they are actually using three separate but integrated technologies. First, React and ReactDOM are the JavaScript libraries that power the automatic updating of a component based on changed state. They also define how components are created. Second, a language called JSX is used to inject HTML-like markup directly into JavaScript. The creators of React believe in keeping both the structure of a component (HTML) and its behavior (JavaScript) within the same file. JSX is a technology that allows you to author the HTML of a component directly within the component’s JavaScript code. However, at least for now, browsers don’t understand JSX and a third technology, Babel, is used to convert the JSX syntax into regular JavaScript methods as part of the build process. While it is possible to use React without using JSX or Babel, much of the functionality is lost, and you will rarely find such a setup in the wild.

Components are the central building until behind React applications. Each component is a small, self-contained block of Javascript that defines the structure (HTML), look (CSS) and behavior (CSS) of a visual element. The SPA example from earlier contains components for each of the three navigation sections (home, stuff, and about), as well as an overall parent component for the app itself. While React can handle the displaying of these components, it doesn’t directly handle how URLs are processed and changed as components load. The back and forward buttons in our SPA example work because we trick the browser into thinking that a new URL is used when a link is clicked. The library responsible for handling this is called, appropriately, React Router.

Between the components, CSS style sheets, JavaScript modules, and other resources, even a simple SPA can be made up of dozens of files. While having all of these individual files makes developing and refactoring much easier, they also make deployment more difficult. One solution for deploying such a site is to use a bundler such as Webpack. Geared especially for SPA’s, Webpack takes all of the various resources used for your application and produces a single JavaScript file that contains everything needed for the app. This makes deploying the site very easy. Webpack also has options for automatically minifying the resulting code, making it more efficient for clients to download and process. In this guide, we will be using Webpack at the center of our build toolchain. In addition to bundling the files, Webpack also has a development server that will monitor the build for changes and automatically rebundle the file and serve it up as a webpage. Using the development server allows us to see code changes reflected in a browser in near real-time.

Finally, instead of installing and managing each of these technologies, we will be using NPM, or Node Package Manager. Node is a popular platform for running JavaScript code on a back-end server. However, Node also comes with NPM which is the most widely used package manager for both client and server sided web applications. While this guide won’t cover the use of Node, we will be using NPM to configure our project and to install all of the above tools and frameworks.


Frameworks and Libraries Summary

  • React and ReactDOM: Core framework for creating React apps and components
  • React Router: Controls how React responds to URL changes
  • JSX: A markup langague injected into JavaScript that is nearly identical to HTML
  • Babel: Transpiler that converts JSX and newer versions of JavaScript into the older, widely supported JS4
  • Webpack: A bundler and build tool that will join multiple assets together into a single JavaScript file
  • NPM: Popular package manger and script runner for both client and server sided web apps


Installing dependencies with NPM

When this guide was published, the following library versions were being used.

  • React: 16.2
  • React Router: 4.2
  • Babel: 6.26
  • Webpack: 4.1
  • Node: 9.4


Install Node

As mentioned in the previous section, we will be using NPM to install and configure all of our tools. One of the great things about NPM is that it makes cross-platform development very simple. Once a build environment is configured, the configuration can easily be copied to any environment that supports Node and NPM. The simplest way to install NPM is to install Node. This is the only piece that will vary based on the operating system.

Windows

There are a few options for installing Node on Windows. First, you can download and run the Windows Installer from https://nodejs.org/en/download/. Choose the .msi file. Alternatively, if you have Choclatey installed you can just run the command choco install nodejs -y.

Ubuntu/Debian

Node is installed by default on many versions of Linux, including Ubuntu. To find out if you have Node installed, use the command which node. If it is not installed, or you want to upgrade to a new version, run apt-get install nodejs. If you are using another variant of Linux, there should be a similar command for your version’s application package manager.

OSX

Setting up Node for OSX is a little more involved, as the the pre-built installer often does not automatically add Node to the system $PATH. The simplest alternative is to first install
Homebrew, an OSX package manager, then run brew install node


Create a file structure and initalize the project

Once Node is installed, we are ready to begin setting up our project and development environment. There are numerous ways in which to organize the file structure of a project, but for this guide, we are going to use a shallow directory structure that separates our files based on type. Navigate to a suitable location on your computer and create a new folder called “spa-example”. This folder will serve as the root of our project, and within the rest of this guide, all other paths will be relative to this. For example, if you created a directory "C:\Users\john\My Documents\spa-example\src", the guide will refer to this directory simply as ./src. Once the root directory is made, create sub-directories as follows:

 + spa-example
     + dist
     + src
         + css
         + html
         + js

Next, return to the root directory and run the command npm init. This will run NPM’s initialization script, and you will be prompted to answer some questions about your project. You can accept the defaults for each question. Don’t worry too much about the details or setting up entry points, as this will be covered in the Webpack section. The script will create a new file in your project root called package.json. This is one of three key configuration files that our project will be using. Go ahead and take a look at the file. You should see key-value pairs that correspond to the questions you were just asked. We will be working with this file more closely later.


A note on editors and IDEs

This guide assumes that you are using either an advanced text editor or an IDE for your project. While this is not a requirement, using an editor that has features such as syntax highlighting and langague definitions makes working with React and JSX much easier. I recommend using either the free or paid versions of JetBrain’s WebStorm. Other good lighter-weight editros include VS Code, Atom, Notepad++, or Sublime. These alternate editors often have framework specfic plugins for React and the other technologies our project will use.


Using NPM

There are a few general options to consider when using NPM to install a package. The first is whether to install the package locally or globally. For our project, we will be installing all packages locally. This means that the resources and files for a package will reside in ./node_modules. This directory will be created automatically when we install the first package. Global package installs are useful if you are going to be using the same package across many projects. For instance, if you worked in an environment where all of your projects used React, then it might make sense to install React as a global package.

The second general configuration option is to specify the environment in which the package will be used. If a package is only needed during development, then we can make NPM aware of that when we install it. That way, if we move our project to a production environment, and ask NPM to automatically download and install all required packages for deployment, NPM will not download and install the development only packages. In our project, Babel is a good example of a development-only package, since it would not be needed in production. Really, since we are going to use Webpack as our builder, all NPM packages can be installed for development, since Webpack will take care of bundling all of our dependencies.

The general format of the command is:

 npm install --save-dev package1 package2 ... // installs as development dependencies
 npm install package1 package2 ...            // installs as producttion dependencies

Using the above commands will install the latest version of the package or packages from the NPM repository. Most of the packages that we install depend on additional packages to function and NPM will also fetch and install these as well. In addition to installing the package, NPM will create an entry in ./package-lock.json. This file lists all of the packages and versions required for the project. If you need to migrate your project to another environment, you can just copy the two package files, then run npm install with no arguments, and NPM will read the package-lock.json and install everything mentioned.


Install required resources

When installing the required dependencies for our project, you can either install all of the packages at once, or you can install them only as they are used. For ease of reading, all of the needed packages will be listed below, along with their suggested dependency type. If you want to install them as you read the guide, just refer back to this list as needed. Remember that you need to run the install command from the root folder of your project..

    //commands
    npm install [package-1] [package-2] ... [package-n]
    npm install --save-dev [package-1] [package-2] ... [package-n]

    // React
    react
    react-dom
    react-router-dom

    //Babel and Babel presets
    babel-core              --save-dev
    babel-cli               --save-dev
    babel-preset-react      --save-dev
    babel-preset-env        --save-dev
    babel-preset-stage-2    --save-dev

    // Webpack and development server
    webpack                 --save-dev
    webpack-cli             --save-dev
    webpack-dev-server      --save-dev

    // Webpack loaders
    babel-loader            --save-dev
    html-webpack-plugin     --save-dev
    html-loader             --save-dev
    css-loader              --save-dev
    style-loader            --save-dev

Depending on the install order, you may see some warnings about depreciated packages, or skipped optional dependencies. These are safe to ignore. You can confirm that the packages were installed by running:

    npm list -dev -prod -depth=0

This will produce a list of all of the top-level packages for both production and development environments. Note that when this article was written, there is currently a bug in NPM list that will show errors if optional dependencies were not installed. It is safe to ignore these errors. See NPM issue #19393 for more information. After the packages have all been installed, we are ready to set up Webpack and our development server.


Configuring Webpack and the development server

Web technologies such as JavaScript, HTML, and CSS don’t have traditional compile steps. The written code can be run directly in the browser without modification. However, compilers from traditional languages also serve as linkers, and in this manner, Webpack can be thought of a kind of compiler. As we make changes to our code, we will use Webpack to bundle that code, along with any needed dependencies, into one or two files that we can then test and distribute. Using Webpack in this manner makes the distribution of code much easier. In addition, we are going to set up a development server so that we can see the changes we make in near real time. The server will scan our directories and files for changes, then rebuild the Webpack output as needed.

Webpack can be used immediately, with almost no configuration needed. But, by default, Webpack only looks to bundle JavaScript code. For our project, we want to bundle HTML and CSS as well, and for that, we will need to create and modify a configuration file. But first, let’s see Webpack in action. The easiest way to run Webpack is to create an NPM script for our project. While we could run Webpack directly, using a script means that we don’t have to worry about the actual location where the Webpack binaries are installed, or remember to type certain parameters every time.

Open ./package.json and find the “scripts” attribute. The value of this attribute is a JavaScript object that contains key/value pairs. The key in each entry is the name of the command, and the value is the command itself. Since this is a JSON file, both the keys and values must be double-quoted strings. Once open, add the following script:

    "scripts": {
    ...,
    "build": "webpack --mode development"
    }

By default, Webpack looks for a file called “index.js” inside of your project’s ./src directory. Webpack uses this file as the entry point for your project. Any other files or imports referenced by index.js will be incorporated into the build. Go ahead and create the file, and add some simple code:

    // ./src/index.js

    alert("index.js loaded");
    console.log("index.js loaded");


Now, from the the project root, execute the command npm run build to run the new script. After a couple of seconds, you will see some output on the console telling you what Webpack did, which files it bundled, and what output was produced. Your ./dist directory now contains a file named main.js. Open it up to see what Webpack produced. You should see a long list of commented out code, followed by an eval("alert(...)") statement near the end. That statement contains the bundled code from your index.js file. If you cringe a little inside with seeing the use of eval, you are not alone. While using eval is strongly discouraged under most circumstances, Webpack uses it in a way that ensures that only code you wrote can be executed. Without the use of eval, web pack would produce less efficient bundles.


Enhancing the Webpack build

While the defaults for a Webpack build might work for some projects, we need some additional behavior to include HTML and CSS files. To do that, we first need to create a WebPack configuration file. Unlike NPM which uses JSON for its configuration, WebPack uses an actual JavaScript file. Within your root directory, create a new file ./webpack.config.js. While we are at it, let’s create some other new files as well. We want our project structure to look like this.

 + root
     + dist
     + src
         + CSS
             - test-app.css
         + html
             - index-template.html
         + js
             - test-component.js
         - test-app.js
     - package.json
     - package-lock.json
     - webpack.config.js

Notice how we are organizing our files. The file test-app.js sits in our src folder. This file will serve as our entry point for a test application. It replaces the index.js file from the previous section. Next, within the CSS, HTML, and JS folders we create files that will ultimately be referenced by test-app.js. These files will be the meat of our application, containing all of the logic and styling. Once the files are created, add some basic content to each so that we can verify everything is working.

    /* ./src/css/test-app.css */

    .blue-square {
        height: 300px;
        width: 300px;
        color: blue;
    }

Just a simple CSS class that will style a large blue rectangle when applied to an element.


    <!-- ./src/html/index-template.html  -->

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Test App</title>
    </head>
    <body>

    <div class="blue-square">Hello</div>

    </body>
    </html>

This is about as simple as we can get for an HTML5 template. Although many browsers will let us omit things like the meta and title tags, the HTML5 specification requires them. Our body contains only a single div element with the blue-square class.


    /* ./src/js/test-component.js  */

    alert('test-component loaded');

Our test components will typically load additional scripts and style sheets as needed, but for now, we are just displaying an alert.


    /* ./src/test-app.js  */

    import "./css/test-app.css";
    import "./js/test-component";

    alert("test-app loaded");

Here we start to see the usefulness of Webpack. This file serves as our entry point and we use it to import both the style sheet for our app, as well as any components or sub-modules. Notice that we are using the import keyword. This is one of the new language features that ES6 brings to JavaScript. WebPack makes use of this by analyzing all of the imports used in a project, and then efficiently deciding what code needs to be included in the bundle. Since we are running Webpack within Node and NPM, we could also have used the require() function. However, since require() can be used for dynamic loading, it is much harder for WebPack to optimize bundles. Unless we need to dynamically load content, we will always use the import statement to load resources.

Now that we have our files and some content in place, we need to tell Webpack how to handle each of the file types. Open up webpack.config.js and insert the following empty template:

 // requires


 module.exports = {

     entry: {

     },
     output: {

     },
     module: {
     rules: [

     ]
     },

     plugins: [

     ]

 };



Let’s briefly cover what each of these sections is for. First, the requires section will contain JavaScript references to external libraries that the Webpack configuration will utilize. Next, entry is an object containing s list of entry-points for Webpack to look for. Although Webpack is typically configured for only a single entry point, we are going to create multiple entry points so that new modules can easily be added to our project. Each entry is in the form of entry-name: 'path/to/file'. The output section contains another object, but this time it will only have a single output. However, since we are defining multiple entry-points, we will use the entry-name we defined earlier to prefix all of our outputs. This way, each entry-point will correspond to exactly one output file. Rules is an array that will contain a description of which types of files to look for, along with what WebPack should do when the file type is encountered. We will create three rules, one each for HTML, CSS, and JS. Finally, the plugins section contains a list of third-party plugins that need more functionality than can be provided by rules. Each plugin in this area must be loaded in the requires section at the start of the file. We will be using the HTML Plugin.

Here are the code snippets that we will be adding to each section:

    /* requires */
    const HtmlWebPackPlugin = require("html-webpack-plugin");
    const path = require('path');

The path module is used later to return an absolute path to our project’s root folder. Since the path is evaluated at run-time, we can freely move our project to a new location without having to update any hard-coded file locations. The Html plugin is used in the plugins section below.


    entry: {
        testApp: './src/test-app.js'
    },

This is where we define the entry point for our component. We are giving the point both a name and a file. The name of the entry is also called a chunk, and we will use named chunks in the HTML plugin section to specify which portions of our project belong to which HTML file.


    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },

Here we use the imported path function to specify that we want our output to go to the dist directory located under __dirname, which is a reference to the project’s root folder. We further specify that a single output file should be produced for each entry and that the name of the file will be the name of the entry chunk. So in our current configuration, Webpack will output the file ./dist/testApp.js. If we had more entry points defined, more .js files would be produced.


    rules: [
        {
            test: /\.js$/,
            exclude: /node_modules/,
            use: {
                loader: 'babel-loader'
            }
        },

        {
            test: /\.html/,
            exclude: /node_modules/,
            use: {
                loader: "html-loader",
                options: {minimize: false}
            }
        },

        {
            test: /\.css$/,
            exclude: /node_modules/,
            use: ['style-loader', 'css-loader']
        }
    ]

Although this is our largest section, don’t let all of the braces and brackets distract you. We are really just adding three rule entries, each of which shares a common pattern. Webpack configurations can use arrays (square brackets) and object literals (curly braces) interchangeably. When a single item is being specified, an object is used. When multiple items are being specified, either an array of strings or an array of objects is used.

Each rule contains three sections. The test value contains a regular expression pattern that is used to match against files. Each of our regexes says to look for files that end with a specific abbreviation. The next attribute, exclude, also contains a regular expression. This time the expression will match against any folder named node_modules and will not look inside for files to apply the rule to.

Finally, the use value tells Webpack which loader or loaders to use. We are telling Webpack to use Babel for .js files, the Html loader/plugin for .html files, and the style and CSS loaders for .css files. We are able to use Babel here because it was installed in an earlier section. Babel comes with some default presets that Webpack can take advantage of. We will customize Babel and add some additional presets when we setup React. Note that for .css we are using an array to specify two different loaders. Both must be included and the order is important. Also, for .html files, we are specifying an option to the loader which will prevent Webpack from minifying the final HTML. This makes debugging easier, and since the majority of our content is going to be written in React/JSX anyways, our HTML files will be too small for minifying them to really matter.


    plugins: [
        new HtmlWebPackPlugin({
            template: "./src/html/index-template.html",
            filename: "./index.html",
            chunks: ['testApp']
        })
    ]

Here we instantiate and configure a new plugin for Html. Notice that we are using the constant that we imported at the top, and then passing in an object as the single constructor parameter. The template value tells the plugin where to look for top-level HTML structure. We then provide the name of the output file and specify which chunks we want the file to include. The chunk we are including is the name of the entry-point we defined earlier. This will cause the plugin to automatically insert a link to the testApp.js file that is produced when we build.



Here is the full configuration file:

const HtmlWebPackPlugin = require("html-webpack-plugin");
const path = require('path');


module.exports = {
    entry: {
        testApp: './src/test-app.js'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader'
                }
            },

            {
                test: /\.html/,
                exclude: /node_modules/,
                use: {
                    loader: "html-loader",
                    options: {minimize: false}
                }
            },

            {
                test: /\.css$/,
                exclude: /node_modules/,
                use: ['style-loader', 'css-loader']
            }

        ]
    },

    plugins: [
        new HtmlWebPackPlugin({
            template: "./src/html/index-template.html",
            filename: "./index.html",
            chunks: ['testApp']
        })
    ],
};


Once everything is in place, repeat the npm run build command and look at the results. Webpack is pretty verbose when it comes to reporting any build errors. If you get any, check both the Webpack config file as well as the contents of the four files we are bundling. Once the build complete, you will see that two files were emitted: testApp.js and index.html. Go ahead and open index.html is any browser. You should get two alert messages telling you that the components were loaded, and then see a blue square. If either of the messages or the square is missing, double check the filenames used in your import statements. If these files don’t exist, the import will fail silently.

Congratulations, you now have a working build chain that will serve as a foundation for React and other web projects. From here on, we will be using this build as a template for creating our SPA.


Creating a development server

At this point, we could move on to setting up Babel and React, then start building our app. However, there is one more Webpack enhancement we can perform that will make developing our app much easier. Right now we have to manually run a build script everytime we make a change. Instead, we are going to set up a small server that will continuously scan our project for changes and building when necessary. The server will also host the dist folder so that we can view the changes in our browser of choice. Using the dev server, we will be able to see our changes reflected in the web app in near real time.

First, we need to add a new top-level section to our webpack.config file. The section can go anywhere, but let’s place it right after plugins. The modified file should look like this:


    module.exports = {
        entry: {...},
        output: {...},
        module: {rules: [...]},
        plugins: [...],

        devServer: {
            historyApiFallback: true,
            open: true
        }
    }

The open attribute tells the server to open our app in a new browser when it stars. The historyApiFallback attribute relates to how routes are handled by the server and will come into play when we start building the components of our SPA.

Next, we need to create a new npm script that will run the command to start the server. Open package.json and add the following to the scripts section:

    scripts: {
        ...
        "dev-server": "webpack-dev-server --mode development"
    }

Pretty simple, right? The command simply starts the server and tells it to use the development profile when building the bundle. Go ahead and start the server by issuing the command npm run dev-server. The first time the server runs, it will take a little while, but after a minute or so a new page will open in your default browser. The URL will refer to localhost, and will likely be running on port 8080. The ./dist directory is the root of the server, and since we named our HTML output index.html, the browser will automatically open and display the page. You’ll see the familiar alert messages and blue box.

Take a couple minutes to try changing each of the four files that make up the bundle. Replace the alert message with console.log (and then see the results in the Chrome or Firefox development console), change the color box within the CSS file, or duplicate the box by adding a new div element to the HTML template. In each case, as soon as the file is saved, the dev server will quickly rebuild the page and it should automatically reload in your browser.

Many IDEs, such as WebStorm, automatically save files as you edit them. The save might happen when you close a file or navigate to another window. You can usually press ctrl+S on the keyboard to force a save and rerender the page. You will need to keep the dev server open in its own dedicated terminal. You can monitor this terminal for any errors that might cause the Webpack build to fail. If a bundle fails, then the page will not reload. Other errors which don’t cause the bundle to fail will be handled by the browser and outputted to the console. On most browsers, you can press F12 to access the development console.


Building a React component with JSX

We are now ready to move on to using React. If you have never used React before, then this section will present a simple introduction. However, this guide is not designed to go into depth about the React framework, and many details of React will not be covered. While reading this section I suggest having a tab open to the official React documentation. React provides both tutorials and reference sections and is the best place to go to when you have questions.

The elemental building block of a React app is a component. Components are a way to encapsulate the structure, data, and behavior of a visual element on the screen. Components are typically nested within each other in much the same way that a traditional websites nest divs, spans, and other elements. In fact, most leaf components in React are made from standard HTML elements. Let’s take a look at how our blue box from earlier would look as a red box component in React.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 import React from 'react';
 import ReactDOM from 'react-dom';

 class Square extends React.Component {

     render() {
         const redSquareStyle = {
             height: 150,
             width: 150,
             backgroundColor: 'red'
         };

         return (
             <div style={redSquareStyle}>Hello</div>
         )
     }
 }

Although it is only 18 lines of code, the above-snipped packs in a lot of what makes React, React. Lets’ take a look at each piece.


 import React from 'react';
 import ReactDOM from 'react-dom';

These are the core components of React. Almost every component in our module will import both of these. The React import is needed so that we can make our own classes that extend React.Component. We will see ReactDOM being used in the next section


 class square extends React.Component {
     render() {
         ...
     }
 }

The newer versions of React have fully embraced ES6, and being able to define classes in this manner is a core ES6 feature. Extending React.Component is what gives our Components special behavior. All classes that extend React.Component must also implement render(). This method is what React uses when it needs to display or update the display of a component. The magic of React is that it dynamically determines when to call each component’s render() method in response to a change in state. When a parent’s render() method is called, it will also trigger changes in any children, and their render() may be called as well.


    const redSquareStyle = {
        height: 150,
        width: 150,
        backgroundColor: 'red'
    };

Here we are defining a style object for use in the JSX code below. React allows us to use both inline styles and normal stylesheets based on class names. Both approaches have their uses. The general rule of thumb is to use attached CSS sheets and class names, but for a small example like this, using inline style objects works well. The object is simply a list of key-value pairs that match up to CSS attributes. Since we are writing this in JavaScript, we must use camel casing for all attribute names. Thus background-color becomes backgroundColor. All of the other CSS attributes follow the same form.

Note also that we are able to leave off the “px” designation for the height and width. React will default to using pixels for all CSS properties that support it as a unit. If we want to specify a different unit, such as percent or em, then we must treat the value as a string, e.g. height = "50%".


    return (
        <div style={redSquareStyle}>Hello</div>
    )

Here is our first taste of JSX. render() must return a single element or component and so we are returning a new div element using something that looks very much like HTML. When we build our component, Babel will turn this JSX code into a sequence of JavaScript methods beginning with document.createElement("div", style=...").

There are a few key differences between JSX and HTML. First, just like in the style object above, when writing JSX we must camel-case all attributes since JavaScript does not allow dashes in names. Second, some of the JSX attributes have different names. For instance, since class is a reserved word in JavaScript, we must use the attribute className in our JSX code. Finally, some attributes use a different syntax than we would expect with HTML. For example, in HTML we could change an inline style like this: <p style="color: blue;">hello</p>. But this wouldn’t work in JSX. Instead, we have to use a style object to set inline styles.

JavaScript is a powerful language, and one of the biggest reasons that the creators of React decided to write all of their HTML inside of JavaScript code was so that their HTML could access all of the features available to JS. When writing in JSX, you can inject any JavaScript expression by placing it inside curly braces. In the example above, we inject our style object into the style attribute by wrapping it in curly braces. This works because all of the JSX code is turned into JavaScript by Babel. Using curly braces to write JavaScript code inside JSX is one of the distinctive features of React, and it’s something that comes up in some form or another almost every React component. Looking at these components can be a little confusing at times. Just remember that the stuff in curly braces is regular JavaScript.



Now that we have a simple React module, go ahead and overwrite the contents of ./src/js/test-app.js with the code from above. Ready to see all of our work pay off? Assuming you still have the dev-server running, save the contents of the file and…

We watch the component fail to build. Webpack appears to have a problem with our JSX. When it encounters the line <div>...</div> it throws an error and quits. The reason for this is that even though Webpack is configured to use Babel out of the gate, we haven’t told Babel what to do with JSX code.


Configuring Babel to process JSX

Setting up Babel to work with JSX and React takes only a couple of lines of configuration. But first, we need to make a new configuration file in our root folder called .babelrc (the dot before the name is important). Make sure the file is under the root directory and next to (at the same level as) webpack.config.js.

Once the file is created, open it and add the following contents:

    // ./.babelrc

    {
        "presets": ["env", "react", "stage-2"]
    }

This file tells Babel to use three different sets of rules when deciding how to transpile one type of JavaScript code into another. The env preset is the default and causes Babel to transpile code into ES5 (also called ES2015), which is the JavaScript version understood by every browser. Actually, all modern browsers now support ES6, but for compatibility with older browsers, we will stick with ES2015. There is some performance loss associated with transpiring ES6 into ES5, and if you know you only need to support certain newer browsers, you can customize the “env” preset to only transpile the missing features. See Babel’s documentation for more information.

The next preset, react is what tells Babel to transpile the JSX code. You can actually use JSX outside of React, but the react preset includes JSX support by default. Last, stage-2 adds support for some proposed specifications to the next version of JavaScript, ES7. Namely, it adds support for class properties, which we will cover in more detail later. Browsers will natively support class properties in the future, but for now, we are using this preset to allow us to write cleaner and clearer React code.

Save the changes to .babelrc and then restart your dev server. Any time we make changes to either the Babel or Webpack configurations, the server must be restarted. You can stop a running dev server by pressing ctrl+c. Once it has stopped, start it back up with npm run dev-server. A new tab will open and we see . . . the same blue box as before. That doesn’t’ look right. We were expecting to see the red box specified by our React component. If we look at our dev server terminal we can see that the build was successful, so our JSX is being transpired correctly.


Rendering a component

The issue is that while we successfully created a component, we haven’t yet told React where to display it. Essentially, we have a nice red box sitting in memory, but no code to cause it to be shown on our webpage. Let’s fix that. First, we need to edit our HTML template to create a container for our React component. One of the ways that React aims to be modular is in how it is incorporated into websites. While most people use React to create the entirety of their content (e.g. a SPA), React can also be used as only a single element within a larger page. To meet both of these needs, a top-level React component must belong inside another element.

Open up ./src/html/index-template.html and edit the body section so that it matches this:

    <body>
        <div id="react-container"></div>
    </body>


Creating our container as a div without any class names or styling allows the React component inside of it to grow as large or small as it needs to. It also means that we can later add additional HTML elements to our page without messing up anything we did with React.

Now, we just need to tell React to render our Square inside this element. To do so, open up ./src/js/test-component.js and add two lines at the end:

        ...
        class Square extends React.Component {
            ...
        }

        const target = document.getElementById("react-container");
        ReactDOM.render(<Square/>, target);

The first of these lines gets a reference to the div container we just made. The second line is much more interesting. We are again using JSX, but this time we are specifying a component instead of an element. The distinction between the two is very important. When a component is created in JSX, the JSX code gets transpired into something like new Square(...). It is custom in JavaScript to begin all classes with an uppercase letter. In React and JSX, it is required. If we named our component class square and used ReactDOM.render(<square/>, target) our code would not work.

Once the changes are made, save the file and watch the dev server finally produce a stunning red square on the page. Just to prove that it’s real, change some of the values in the redSquareStyle and ensure that the page updates.


Making a few improvements

We now have a React component made up of both JavaScript and JSX that is correctly being transpiled and rendered to the screen. Before we leave this example behind, there is one more change we want to make. Earlier I mentioned that it is preferable to use CSS style sheets over inline style objects, but it seems that our React component doesn’t do this. Let’s correct that.

First, copy the key/value pairs from our component’s redSquareStyle into a class that lives in thetest-app.css file, making sure to add the appropriate colons and semi-conols as needed. Converting back-and-forth between CSS style rules and JSX style objects occurs frequently in React. The modified .css file should look like this:

    /*  ./src/css/test-app.css  */

    .blue-square {
        height: 300px;
        width: 300px;
        background-color: blue;
    }

    .red-square {
        height: 150px;
        width: 150px;
        background-color: red;
    }



Next, modify our test-component file by doing the following:

  • Import the updated style sheet into our component by adding import './../css/test-app.css' to the top of the file. Pay carful attention to the path that we are using for the import. All imports are relative to the file they come from, so to import the .css file, we start in the ./src/js/ directory, then move up a level before referencing /css/test-app.css.
  • Delete the redSquareStyle object and the style={...} attribute from our Square component.
  • Add the attribute className="red-square"
  • Finally, let’s change our return statement make two squares, one of each color. Since render() can only return a single element, we must wrap the two div’s inside a parent. We should also reanme our componet to Squares

Our component should now look like:

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './../css/test-app.css';

    class Squares extends React.Component {

        render() {
            return (
                <div>
                    <div className="red-square"> </div>
                    <div className="blue-square"> </div>
                </div>
            )
        }
    }

    const target = document.getElementById("react-container");
    ReactDOM.render(<Squares/>, target);

Save the changes and watch our webpage update. Our test-component is now complete. It will serve as the template on which we will build our SPA.


Summary

  • Components are the building block of React
  • Components are created by using a class that extends React.Component
  • Each component must implement a render() method, and this method must return a single element or a single component
  • Most components are made using a mix of JavaScript and JSX
  • JavaScript statements can be injected anywhre into JSX by surrounding the statement with curly-braces
  • JSX can style an elment either inline with style objects, or with imported stylesheets and rules
  • To display a component, you must render it to a target container that exists within the DOM.


SPA: Structure and Component Selection

While React components can be organized in many different ways, the standard structure is to have an outer component that serves as a container. This component is responsible for creating and maintaining any state that is shared among its children. The structure of the application container is usually limited to creating and naming the sub-components that make up the app. The sub-components are then created and organized in a way that allows them to exist independently from both each other and from their parent. In this manner, React’s organizational structure reflects the standard principals of object-oriented design.

It is customary to sperate each component into its own file, and then place all of these component files into the ./src directory. However, to keep things simple, we will place all of our components into a single test-components.js JavaScript file. Still, before beginning to build this file, we want to decide what components we need and how they should be organized.

Take a look again at the SPA demo and think about how you might break down the application into reusable parts. The degree with which to decompose an app into smaller pieces is based on a number of factors, and there usually isn’t a single right answer. For the purposes of our example, we are going to create the following four components:

  • App: This will serve as the container for our SPA
  • Home: Contains the main conent and is what visitors will first see
  • Stuff: Some random text
  • Contact: A simple display of contact information

In terms of display, since the App component is our outer container, it will always be shown on the page. It will also contain the navigation menu. The App component will have a dedicated area that contains the three sub-components of Home, Stuff, and Contact. However, depending on which nav link is active, only one of these components will be rendered and displayed. It will be the job of React-Router to handle this behavior.


File Structure

Before writing any of the code, let’s create the three new files that our SPA app will be using. First, we need to create a new entry point for Webpack to reference. This entry point will live directly under the ./src directory. Let’s call this file spa-app.js. Since we previously configured Webpack to support multiple output bundles, we can leave our existing ./src/test-app.js alone.

Second, create a ./src/js/spa-components.js file. This is where the bulk of our code will be. The reason to separate this file from the entry point is so that we can use our SPA app in a different, non-Webpack build environment if we wanted to. If you want to follow the standard React organization pattern, you can also create three additional files to separate the components: Home.js, Stuff.js and Contact.js. Note the naming convention. When a JavaScript file contains only a single component (or really, a single class), it is standard to name the file after the name of the class, including the upper-case first letter. However, if the file will contain multiple components or classes, then the name should be in all lower-case.

Third, create a new stylesheet named ./src/css/spa-components.css. Or, if you decided to break apart the components into four separate JavaScript files, then create four new CSS files. In general, each component should have its own separate stylesheet. However, since CSS files are often the most heavily edited and customized, many web projects often place all CSS code within a single file. Both approaches are valid, and other than a few extra import statements, choosing one approach over the other makes little difference in the code.

The file and structure for the app should now look like this:

  + root
  + dist
  + src
      + css
          - spa-components.css
          - test-app.css
      + html
          - index-template.html
      + js
          - spa-components.js
          - test-component.js
      - spa-app.js
      - test-app.js
  - package.json
  - package-lock.json
  - webpack.config.js

Once the files are created, let’s add some basic content to each so that we can verify that everything is working. Add the following content:

./src/spa-app.js

    import "./js/spa-components";
    import "./css/spa-components.css";

    console.log("SPA loaded");



./src/js/spa-components.js

    import React from 'react';
    import ReactDOM from 'react-dom';

    import './../css/spa-components.css'

    class App extends React.Component {
        render() {
            return (
                <div>Hello</div>
            )
        }
    }

    const target = document.getElementById("react-container");
    ReactDOM.render(<App/>, target);

The content of each file is very similar to what was used in our test-app and test-component files. It is the minimum needed to create a working React application.



Once the new files have been created, we must update our webpack configuration to reference the new entry point. Inside ./webpack.config add the following lines under the entry and plugins sections:

    ...
    module.exports = {
        entry: {
            testApp: './src/test-app.js',
            spaApp: './src/spa-app.js',
        },


        plugins: [
            new HtmlWebPackPlugin({
                template: "./src/html/index-template.html",
                filename: "./index-test.html",
                chunks: ['testApp']
            }),
                new HtmlWebPackPlugin({
                template: "./src/html/index-template.html",
                filename: "./index.html",
                chunks: ['spaApp']
            })
        ],

        devServer: {
            historyApiFallback: true,
            open: true
        }
    };

Note that in addition to the new lines, we also changed the name of the HTML output file for testApp from “index.html” to “index-test.html”. Since we want the page to automatically load in our browser, we need to reserve index.html for our new application.

Once the changes have been made, restart the dev-server with ctrl+c followed by npm run dev-server. After a few seconds a new page will open in the browser, replacing the colored squares with a simple “Hello”. If you open the developer console, you should see a message indicating that the new entry point was loaded.


Imports

When using the import statement, there are a few things to note. While there are a lot of different ways to use import, there are three common patterns that you will see in this and other web projects.

/* Import a file without creating a reference. Used to import static
   assets such as stylesheets, images, etc.

   This type of import essentially just lets Webpack know that the contents of the file need to be bundled. */

import 'path/to/asset.xyz'
import './example.css'

/* Import a JavaScript library and assign the library's
   default export to a reference */


// when importing libraries, we don't add the .js extension
import Home from './js/Home'

// if added with NPM, refer to it directly by name
import React from 'react'

/* Import a JavaScript library and use ECMA6 destructuring
   to assign multiple references. Creates three new references */

import {
   HashRouter as Router,
   Route,
   Link
} from 'react-router-dom'



Notice how we have designed our import structure. Keeping track of what files and modules are imported where can become difficult for larger projects, but breaking projects into small components helps. In the SPA we are creating, we have a root entry point that is importing a single file. This file is either spa-components.js, or App.js if you are using separate files for each. If we had elements on our site that were not being controlled by React, then we would also import them here. We might also import a stylesheet if we wanted to override any global style objects. However, we should not import anything related so a specific React component, as that is the responsibility of later modules.

Next, inside each component or components file, we import the libraries and stylesheets needed only for that component. If a library or file is shared among multiple components, then it will need to be imported multiple times. However, Webpack will realize this and only a single copy will be included. This approach, as opposed to a centralized imports file, allows React components to be more modular, leading to smaller bundles and quicker load times.


SPA: Building the components

Now that we have our structure in place, we can start building the individual React components that will make up or SPA. While we can create the components in any order, let’s start with the simplest ones first. Home, Stuff, and Contact will make up the majority of the content of our site, and since these components serve mostly as content holders, they will the the quickest to create.


Home, Stuff, and Contact components

class Home extends React.Component {
    render() {
        return (
            <div>
                <h2>Welcome!</h2>

                <p>The Lady of the Lake, her arm clad in the purest shimmering samite, held aloft Excalibur from the bosom of the water, signifying by divine providence that I, Arthur, was to carry Excalibur. That is why I am your king.</p>
                <p>And this isn't my nose. This is a false one. A newt? The nose?
                <p>He hasn'
t got shit all over him. <strong> But you are dressed as one… Shut up!</strong>
                <h2>Shh! Knights, I bid you welcome to your new home. Let us ride to Camelot!</h2>
                <p>Why do you think that she is a witch? Ni! Ni! Ni! Ni! We want a shrubbery!! No, no, no! Yes, yes. A bit. But she's got a wart. Ni! Ni! Ni! Ni!</p>
                <ol>
                    <li>The Knights Who Say Ni demand a sacrifice!</li><li>And the hat. She'
s a witch!</li><li>…Are you suggesting that coconuts migrate?</li>
                </ol>
            </div>
        )
    }
}

Here we are creating a component by making a new class that extends React.Component. The class has only a single method, named render(), and that method is returning a single HTML element via JSX. Note that the return statement is wrapped in required parentheses. Inside this single element is simply some random Monty Python quotes that were generated from fillerama.io.

Our Home component doesn’t really do much, and it seems overkill to have it extend React.Component when it is doing nothing more than defining a render() method. React refers to these as “pure components” and provides a simpler mechanism for their creation. Since our Home component may need to later implement state or other React features, we will leave it as a class component, but let’s use the simpler syntax for creating Stuff and Contact.


 const Contact = (props) =>
     <div>
         <h2>Have questions?</h2>
         <p>Use the contact form on my website at <a href="https://blog.echogy.net/#about">https://blog.echogy.net</a></p>
     </div>;


 const Stuff = (props) =>
     <div>
         <h1>Simpsons Quotes:</h1>
         <h4>This is the greatest case of false advertising I've seen since I sued the movie "The Never Ending Story."</h4>
         <p>Fat Tony is a cancer on this fair city! He is the cancer and I am the…uh…what cures cancer?</p>
         <p>Oh, I'
m in no condition to drive. Wait a minute. I don't have to listen to myself. <strong> I'm drunk.</strong></p>
         <p>Your questions have become more redundant and annoying than the last three "Highlander" movies.</p>
     </div>;

Instead of creating a new class that implements a render() method, we are creating a constant that represents the render() function directly. Anytime React expects a component, it will also accept a single function and as long as that function returns a single element or component, React will use it as a stand-in for a call to render(). When React calls this function, it will pass it a props object containing all of the key-value pairs that were set when the component was created.

In JavaScript, there are multiple ways to return a function, and the approach above uses the arrow (or lambda) syntax from ES6. Below are two other common ways to accomplish the same. The main difference between the approaches is that arrow functions do not cause the context of this to change. The syntax is also a little cleaner and is the standard choice.

function Contact(props) {
   return (
      <div>
          <h2>Have questions?</h2>
          <p>Use the contact form on my website at <a href="https://blog.echogy.net/#about">https://blog.echogy.net</a></p>
      </div>;
   );
}


const Contact = function(props) {
  return (
      <div>...</div>
  );
}


App component

Now that we have the content components built, we need to create our container and insert the child components into it. Taking a look at the finished example we can see that there are three main areas on the page: a title, a navigation bar, and the page content. Let’s modify our App component to include these three areas, representing each with an appropriate element.

 class App extends React.Component {
    render() {
        return (
            <div id="app">
                <h1>SPA Demo</h1>
                <section id="header"></section>

                <section id="content"></section>
            </div>
        )
 }

Notice that we wrapped all of the JSX code for our application inside of a <div id="app"/> element. This will make styling easier since we can efficiently customize the CSS for all of the standard elements of our SPA. Save the changes we made and watch the development server refresh and update the page. The only thing displayed at this point is the header. Let’s change that by adding the Home component to our App component. To do so, insert the following line:

    ...
    <section id="content">
        <Home/>
    </section>

Although the new line looks almost identical to inserting an HTML element, there is quite a bit of work that React does behind the scene. Notice that we are using a capital letter to reference Home. This is required and serves as a way to tell JSX that the thing you are inserting is a React component instead of an element. When React or JSX encounters the line, it converts it into something similar to this:

const props = {...};
// check to see of Home extends React.Component
if (Home.prototype instanceof React.Component) {
   const componentX = new Home(props);
   return componentX.render();
}
else  // if not, then it is a pure component
   return Home(props);



Now that we have some content, let’s add our navigation menu. We will use an unordered list along with some CSS to change the list layout to horizontal.

// app-components.js

class App extends React.Component {
   ...
   <section id="header">
      <ul className="nav-menu">
         <li className="nav-menu-item">Home</li>
         <li className="nav-menu-item">Stuff</li>
         <li className="nav-menu-item">Contact</li>
      </ul>
   </section>
}


   /* app-components.css */

   #header .nav-menu {
      padding: 0;
   }

   #header .nav-menu-item {
      display: inline;
      list-style-type: none;
      margin-right: 25px;
   }

With the insertion of the navigation menu, the basic structure of our SPA is complete. All that remains is to wire-up the navigation links with their associated components.


SPA: Navigation and Routing

Right now our site’s navigation menu is nothing more than text. In order to make it functional, we are going to introduce a new library called React Router. The term routing is common in web applications, especially SPAs. It refers to how both a client and server handle changes to the URL. In a traditional website, this was exclusively the domain of the browser. When Chrome or Internet Explorer encountered a new URL, it would load a new page. Simple as that. However, with web applications, we usually don’t want a new page to be loaded each time a navigation change occurs. Instead, we want only a portion of the current page to change.

There are many ways to achieve this functionality, but one of the simplest is to use React Router to dynamically load and unload components in response to events. While the events in our SPA will be limited to the activation of a navigation link, many other types of events are possible. These events, along with good component design are what allow a web app to function in a similar manner to a desktop or mobile application.


Creating the Router

To begin, install React Router from NPM via npm install react-router-dom. We are chossing the DOM (Document Object Model) version of the router, which is the standard for web browsers. React Router also has versions for creating React Native and other types of applications. Once installed, let’s import the new components that we will be using.



app-components.js

...
import {
   HashRouter as Router,
   Route,
   Switch,
   NavLink,
} from 'react-router-dom'

Routing in React Router is handled by creating Route components that wrap a child component. These child components only render if the current route matches a specified property. The term route refers to the portion of a URL that comes after the domain. For example, if the URL is www.store.com/catalog/item/3, then the current route is catalog/item/3.

However, before specific Route components can be used, they must exist within the context of a Router. While larger projects often have multiple Router components, ours will have only one. And this Router will wrap or App component, allowing us to freely create new routes as needed. To add the Router, make the following changes to the last lines of our SPA. To note, while we could have wrapped App directly inside ReactDOM.render(), creating a new constant is cleaner and allows for a better organization if we want to add additional components or options.

   // app-components.js
   ...
   const routedApp =
   <Router>
      <App/>
   </Router>;

   ReactDOM.render(routedApp, target);



After the component has been added, save the changes and reload the webpage. The content won’t look any different, but you should notice one change: the URL is now different. Previously, the URL to our SPA was just localhost:8080. But now, the URL has a /#/ added to the end. This is the result of having our app controlled by a router; more specifically, a HashRouter.

The DOM version of React Router comes with two different types of Routers. A HashRouter and a BrowserRouter. While the two components are used in the same manner, the underlying mechanics are very different. The BrowserRouter is newer and avoids the some might say, ugly hash symbols cluttering up the URL. The downside is that BrowserRouter requires some level of server-side routing for it to be effective. Without having some help from the server, a web browser won’t know what to do when a user reloads or manually navigates to a page such as www.store.com/products/. In a development environment, we can enable some simple server routing by setting the historyApiFallback property of our Webpack dev server to true (which we did earlier). However, implementing the same behavior on a production server is more involved.

As we build the rest of the routes, try changing HashRouter to BrowserRouter and back again to see the differences when you manually refresh a page, or when you manually enter a URL into the browser.


Adding routes

The behavior of a Route component is primary defined through properties. For a full list of properties and how they can be used, refer to the official React Router documentation. The two most important properties of a Route are path and component. These tell the Router what route to look for and which component to render when that route is found.

Right now our app is set to always display the Home component, regardless of the current route. Let’s change that by wrapping Home inside a new Route component.

   // app component
   <section id="content">
      <Route path="/" exact="true" component={Home} />
   </section>

We create a new Route that will be active only when the URL exactly matches the root route. This means that a URL of localhost:8080/ will match, but a URL of localhost:8080/abc will not. If we omitted the exact property, then the route would match on URL’s such as localhost:8080/stuff or localhost:8080/contact, possibly causing the wrong component to be displayed.

Also, note how we are specifying the component to create and display. In previous versions of React-Router, we would have nested the component as a child of Route, but in the newer versions, we use a property instead. As such, we are specifying only the name of the component instead of using it as a JSX tag.

Committing the change won’t result in any immediately noticeable difference in the app, but try changing the URL to something like localhost:8080/#/test. Before we added routing, such a URL would produce a blank page or 404 error. Now, we see our SPA with a title and nav bar. The reason why this works is subtle but important. Since we wrapped our entire App component inside a Router, App will be rendered regardless of the URL. Anywhere inside App where we want to conditionally display a component, we will include it in a route.

Now that we have a Route, let’s link it to our navigation menu. To do this, we will use a new component called NavLink. While we could build the functionality ourselves using a regular <a> element, the <NavLink> provides some nice visual features. To use it, make the following changes:

   // app-component.js
   <section id="header">
      <li className="nav-menu-item">
         <NavLink to="/" exact='true' activeClassName='active'>Home</NavLink>
      </li>
   </section>


   /* spa-components.css */

   /* add */
   #header .nav-menu-item a {
      text-decoration: none;
      colol: inherit;
   }

   /* add */
   #header .nav-menu-item .active {
      font-weight: bold;
   }

We are doing a few things here. First, we are inserting a NavLink inside of our Home list item, and specifying the to property. The value of this property is identical to the path property in the Route we want to render. And just like inside the Route, we need to specify that we want to match the path exactly.

Next, we tell the NavLink that we want to apply the active CSS class to the element whenever the current route matches. This produces a nice visual change when links are navigated to and away from. Finally, we add some CSS rules to disable the default color and underlying of links in our nav-menu-items, and to apply a bold font to active nav-items.

When the page reloads, you should see the content of our Home component along with a bold and clickable Home navigation link. If you manually change the URL you will see the Home content disappear, and the link text will change to a normal font weight. Using your browser’s forward and back buttons will show these changes as well.

All that remains to complete the functionality of our routing is to add the Routes and NavLinks for the other two sections. For these, since we will be using non-root routes, we don’t need to include the exact property. When finished, our final App component will look like this:

class App extends React.Component {
   render() {
      return (
         <div id="app">
            <h1>SPA Demo</h1>
            <section id="header">
               <ul className="nav-menu">
                  <li className="nav-menu-item">
                     <NavLink to="/" exact='true' activeClassName='active'>Home</NavLink>
                  </li>
                  <li className="nav-menu-item">
                     <NavLink to="/stuff" activeClassName='active'>Stuff</NavLink>
                  </li>
                  <li className="nav-menu-item">
                     <NavLink to="/contact" activeClassName='active'>Contact</NavLink>
                  </li>
               </ul>
            </section>

            <section id="content">
               <Switch>
                  <Route path="/" exact="true" component={Home} />
                  <Route path="/stuff" component={Stuff} />
                  <Route path="/contact" component={Contact} />
               </Switch>
            </section>
         </div>
      )
   }
}

We use one new type of component named Switch. This component is used to wrap multiple Routes. When the Router encounters a Switch, only the first matching Route will be selected. While not technically needed for our simple application, it is recommended to use a Switch anytime you want only a single component to render.

Once the changes are made, take a moment to test out the properties of our app. Navigation should work across all three links, as will the forward and back buttons in the browser. Although the URL updates, no actual page changes will occur, but manually reloading or changing the URL will work as expected. If you’ve been using HashRouter, try changing the imported type to BrowserRouter (or vice versa) and notice the differences in the URL.


Styling

The above code fragments include only the minimal amount of CSS necessary. The full set of style rules for the demo app are included below.

/* spa-components.css */

body {
    background-color: #111111;
    padding: 20px;
    margin: 0;
    color: #00FF41;
}

h1, h2, p, ul, li {
    font-family: Consolas, Courier, monospace;
}

#app h1 {
    text-align: center;
}

#header .nav-menu {
    padding: 0;
    background-color: darkblue;
}

#header .nav-menu-item {
    text-decoration: none;
    display: inline-block;
}

#header .nav-menu-item a {
    color: #FFFFFF;
    font-weight: bold;
    text-decoration: none;
    padding: 20px;
    display: inline-block;
    font-size: larger;
}

#header .active {
    background-color: blue;
    font-weight: bolder;
    border-radius: 35%;
}

#content {
    background-color: black;
    padding: 20px;
}

#content h1, .content h2, .content h3 {
    padding: 0;
    margin: 30px 0 30px 0;
}

#content li {
    margin-bottom: 10px;
}

#content a {
    color: lightyellow;
}


Conclusion

At the completion of this guide, we have a versatile development environment configured to create React applications with the help of Webpack and Babel. While there are dozens or even hundreds of other environments for developing React applications, ours is one of the more common. It is well suited for cross-platform work as both the project and the environment can easily be moved to a new platform with only a few commands to get up and running.

As web browsers continue to adopt the new features of ECMAScript 6 and beyond, there may be less of a need for a transpiler such as Babel. There is even talk of adding native JSX support to Firefox and Chrome. However, if history is any guide, React will likely use the newest JavaScript features before there is widespread support, and transpiling will be required in the foreseeable future.

In addition to setting up a development environment, the goal of this guide was to introduce React and React Router to produce a web application. While this is one feature of React, much of the power of the library comes from how React handles state changes and uses these changes in conjunction with properties. These areas are beyond the scope of this guide but are arguably the most important parts of React. There are some excellent free guides available, including the tutorials that are part of the
official React documentation.

Thanks for reading. I welcome all feedback about this guide, especially any typos, bugs, or technical errors that you might find. If you found this guide helpful, I would appreciate a quick comment letting me know. Please use the contact form below.

About the author

Brian Houle: (blog owner)  I'm a computer science senior at the Metropolitan University of Denver. I love technology of all kinds and have recently become interested in expanding my knowledge of web frameworks.

1 Comment

  1. izle

    January 16, 2021
    Reply

    Thanks Thibaud, Best wishes and good luck. See you soon. Orella Jaime Olive

Would you like to share your thoughts?

Your email address will not be published. Required fields are marked *

Leave a Reply

%d bloggers like this: