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.
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:
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.
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.
When this guide was published, the following library versions were being used.
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.
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
.
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.
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
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:
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.
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.
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:
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.
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..
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:
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.
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:
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:
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.
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.
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.
Just a simple CSS class that will style a large blue rectangle when applied to an element.
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.
Our test components will typically load additional scripts and style sheets as needed, but for now, we are just displaying an alert.
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:
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:
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.
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.
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.
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.
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:
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.
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:
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:
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.
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.
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
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.
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%"
.
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.
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:
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.
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:
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:
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.
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:
Next, modify our test-component file by doing the following:
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
.redSquareStyle
object and the style={...}
attribute from our Square component.className="red-square"
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:
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.
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:
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.
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:
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
./src/js/spa-components.js
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:
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.
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.
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.
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.
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.
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.
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.
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:
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:
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.
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.
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.
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
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.
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.
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.
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:
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:
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.
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.
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.
Your email address will not be published. Required fields are marked *
1 Comment