Vous êtes sur la page 1sur 51

React Eshop

Easily reusable Eshop in React, ES6, and Firebase.


Manav Sehgal
This book is for sale at http://leanpub.com/reacteshop
This version was published on 2016-08-03

This is a Leanpub book. Leanpub empowers authors and publishers with the
Lean Publishing process. Lean Publishing is the act of publishing an
in-progress ebook using lightweight tools and many iterations to get reader
feedback, pivot until you have the right book and build traction once you do.
2016 Manav Sehgal

Also By Manav Sehgal


React Speed Coding

Contents
Easy Start React . . . . . . . . . . . . . . . . . . .
Eshop feature goals . . . . . . . . . . . . . . .
React learning goals . . . . . . . . . . . . . . .
Start React app in three easy steps . . . . . .
Windows and Node . . . . . . . . . . . . . . .
Structure of the React app . . . . . . . . . . .
App.js component definition . . . . . . . . .
index.js root component . . . . . . . . . . . .
package.json dependencies . . . . . . . . . .
Integrating React Bootstrap . . . . . . . . . .
index.html template . . . . . . . . . . . . . .
ESLint for JS guidelines and syntax checking
Flow static type checking . . . . . . . . . . . .
Build and deploy . . . . . . . . . . . . . . . . .
Deploy using Firebase . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
1
.
1
.
1
.
3
.
4
.
5
.
6
.
8
.
8
.
9
. 13
. 14
. 16
. 18
. 20

Magic Behind Scaffold Generator


HTML Webpack plugin . . . . .
Webpack . . . . . . . . . . . . .
Hot Reloading . . . . . . . . . .
Babel and ES6 . . . . . . . . . .
PostCSS and Autoprefixer . . .
ESLint . . . . . . . . . . . . . . .
Other capabilities . . . . . . . .

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

22
22
22
23
24
25
25
25

Product Summary . . . . . . . . . . . . . . .
Eshop feature goals . . . . . . . . . . . .
React learning goals . . . . . . . . . . . .
Product summary design . . . . . . . . .
Product summary schema . . . . . . . .
ProductSummary.js custom component
Prop validation . . . . . . . . . . . . . .
Constructor, state, and bind . . . . . . .
Event handler methods . . . . . . . . . .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

. 26
. 26
. 27
. 28
. 29
. 30
. 30
. 31
. 32

CONTENTS

The render method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


App.js product summary render . . . . . . . . . . . . . . . . . . . . . . .

33
42

Easy Start React


Goal of this book is to recommend the shortest path to writing real-world React
apps. We will code along a single page web app creating an easily reusable Eshop
in React, ES6, and Firebase.

Eshop feature goals


In this chapter we will add first set of features to our Eshop.

Browser tab title to reflect our intended app


Responsive navigation bar with brand and links
App introduction or main call-to-action area
Device specific icons
Meta tags targeting Facebook, Twitter, and search engines

React learning goals


We will also learn following concepts in this chapter.

Scaffold a React app in three steps


Integrate React Bootstrap
Add ESLint in-place notifications to Atom editor
Integrate Flow static type checking
Generate a production ready build for our app
Deploy our app to a local server
Deploy our app to Firebase static hosting

Live demo of final completed Eshop is available on ReactEshop.com1 website.


Source for this chapter is available in our GitHub repo2 . Please feel free to
support us by starring this repo.
1
2

https://reacteshop.com
https://github.com/manavsehgal/react-eshop

Easy Start React

Get the code. You can clone the source for this entire book, change to app
directory, checkout just the code for this chapter, and start the app to launch
the local version in your default browser.
git clone https://github.com/manavsehgal/react-eshop.git
cd react-eshop
git checkout c01-easy-start-react
npm start

Easy Start React

Start React app in three easy steps


Getting started with React is now easier than ever.
Step 1: All you need to do is install Create React App3 , the official scaffold
generator provided by Facebook, to create a React app boilerplate.
Fire up your Mac Terminal (Applications > Utilities > Terminal) to start installing the scaffold generator.
The Create React App scaffold generator can be installed using Node Package
Manager (NPM).
npm install -g create-react-app

Step 2: You can now scaffold a React app boilerplate.


create-react-app react-eshop

The Create React App scaffold generator finishes with this message.
Success! Created react-eshop at .../react-eshop.
...
Happy hacking!

Step 3: Thats it! Now you can run npm start command in the Terminal. This
in turn fires up your browser to render your app at http://localhost:3000 local
address.
3

https://facebook.github.io/react/blog/2016/07/22/create-apps-with-no-configuration.
html

Easy Start React

Default React App

Windows and Node


Are you using Windows?
Unfortunately Windows users face several challenges using Node. Read one of
the largest threads on these Windows-Node issues here4 . We are not suggesting
you invest in a Mac though.
Instead you may want to consider using Cloud based IDE like Cloud95 . Cloud9
comes pre-installed with Node, features intuitive browser based IDE, and offers
terminal access over Ubuntu OS. You can start Cloud9 on a generous free plan.

Do not have Node?


If you get an error running the NPM command or your Node version is not
current you can install or upgrade using installer available at the official Node
website6 .
4

https://github.com/nodejs/node-gyp/issues/629
https://c9.io
6
https://nodejs.org
5

Easy Start React

Structure of the React app


The React app scaffold contains minimal number of files required for a root
component defined in index.js and another custom component called App
defined in the App.js file.
Each component imports its own CSS styles. Images can also be imported within
the components.
react-eshop/
README.md
index.html
favicon.ico
node_modules/
package.json
.gitignore
src/
App.css
App.js
index.css
index.js
logo.svg

<== App Root


<== Default page template
<==
<==
<==
<==
<==
<==

Installed by Create React App


The only app config file
Recommended folders/files to ignore in git
App source lives here
Component specific CSS
Custom component definition

<== Root component definition


<== Import image

The scaffold also contains minimal configuration within package.json and only
one development dependency in the form of react-scripts package.

Easy Start React

App.js component definition


Let us learn React ES6 fundamentals from the generated code.
The App component lives in App.js file. The component is defined using ES6
syntax.
Import. First statement is importing dependencies from React core. The React
dependency is exported using export default so does not require { } braces when
importing. There can only be one such dependency. The Component dependency
is imported using { } braces. There can be multiple such dependencies.
Next statement imports the logo image used within App component. Following
statement imports the CSS styles used by App component.
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

Class definition. ES6 class statement defines the custom App component and
extends Component we import from core React.
class App extends Component {

JSX. The render method returns JSX which looks like HTML. JSX structures React
component hierarchies, connects UI with event handlers, and specifies data
flow between components.
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<p className="App-intro">
To get started, edit ...
</p>
</div>
);
}

Easy Start React

Native components. The HTML-like tags are actually native React components
wrapping the respective HTML tag features. Yes, even <p> is a native React
component wrapping the actual HTML DOM tag for paragraph.
<p className="App-intro">
To get started, edit ...
</p>

Note the subtle differences in use of className instead of class which is not used
as it is a JavaScript reserved keyword.
Composition. React is all about component hierarchies. You will notice two
kinds of component compositions in this example. App custom component
owns div native React component. The div component in turn is a parent to
children div and paragraph components. Subsequent div component identified
by className App-header is parent to img and h2 child components.
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>

Children. Components can be self-closing like the img component or have children components or text like the paragraph and h2 components, with enclosing
tags. These components can then refer to the enclosed components or text
using this.props.children within their own component definition.
<h2>Welcome to React</h2>

Props. React components can pass data from owner components like custom
App component in this case, into their owned components, like the img native
component. This data is passed using what are known as props or properties.
Examples of props passed in this file include className, src, and alt properties.
<img src={logo} className="App-logo" alt="logo" />

We will learn more React ES6 concepts as we code along Eshop app in this book.

Easy Start React

index.js root component


Root component within a folder is identified by index.js file instead of file name
by component class name as in case of App component.
import
import
import
import

React from 'react';


ReactDOM from 'react-dom';
App from './App';
'./index.css';

ReactDOM.render(
<App />,
document.getElementById('root')
);

The root component is importing App component, React, and ReactDOM to


render to HTML DOM target identified by the element id, root in this case.

package.json dependencies
The package.json configuration file represents the project dependencies used
when you run the app or build.
"devDependencies": {
"react-scripts": "0.2.0"
},
"dependencies": {
"react": "^15.2.1",
"react-dom": "^15.2.1"
},

There is only one development dependency, react-scripts. Runtime dependencies which are loaded in the browser include react and react-dom core libraries.

Easy Start React

Integrating React Bootstrap


React Bootstrap7 is a mature library of React components delivering Bootstrap8
CSS styles for React apps. It completely rewrites Bootstrap JS using custom
React components.
You can integrate React Bootstrap in three easy steps.
Step 1. Install React Bootstrap and Bootstrap CSS using NPM.
npm install react-bootstrap --save
npm install bootstrap --save

This will add bootstrap and react-bootstrap as runtime dependencies within


package.json file.
Step 2. Import Bootstrap CSS and optionally Bootstrap theme within index.js
file.
...
import './index.css';
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap/dist/css/bootstrap-theme.css';
...

Step 3. Import and use required React Bootstrap components within your app
component.
Let us rewrite App.js using React Bootstrap custom components replacing all of
React native components.
We start by importing the required components from react-bootstrap. Note
that selectively importing components reduces the runtime size of our app and
in turn improves performance.

7
8

https://react-bootstrap.github.io/
http://getbootstrap.com/

Easy Start React

10

import React, { Component } from 'react';


import {
Grid,
Navbar,
Nav,
NavItem,
Jumbotron,
Button } from 'react-bootstrap';

We no longer need App.css as all styles will come from React Bootstrap.
Next we rewrite the render method. If you are familiar with Bootstrap, you
will notice many similarities in the component hierarchy and naming. You will
also appreciate that the React JSX code is way more concise than plain HTML +
Bootstrap code. This is thanks to good React design patterns following by React
Bootstrap.
class App extends Component {
render() {
return (
<div>
<Navbar inverse fixedTop>
<Grid>
<Navbar.Header>
<Navbar.Brand>
<a href="/">React Eshop</a>
</Navbar.Brand>
<Navbar.Toggle />
</Navbar.Header>
<Navbar.Collapse>
<Nav pullRight>
<NavItem href="//github.com/manavsehgal/react-eshop">
Code
</NavItem>
<NavItem href="//leanpub.com/reacteshop">
Book
</NavItem>
</Nav>
</Navbar.Collapse>
</Grid>

Easy Start React

11

</Navbar>
<Jumbotron>
<Grid>
<h1>Easily Reusable Eshop in React</h1>
<p>Eshop written in React, ES6, and Firebase.</p>
<p><Button bsStyle="success" bsSize="large">
Learn more
</Button></p>
</Grid>
</Jumbotron>
</div>
);
}
}
export default App;

If you are unfamiliar with Bootstrap, we suggest starting with their well documented website, followed by React Bootstrap documentation to note the similarities and differences.
Let us walk through the important bits to understand the new App component.
We are adding two important components Navbar and Jumbotron to deliver the
Eshop feature goals for this chapter.
The Navbar.Header and Navbar.Brand are React namespaced components9
representing children of Navbar parent component.
Note that the Grid component is only available in React Bootstrap, however it is
actually wrapping container component from Bootstrap using bsStyle='container'
default property.
The boolean properties without values inverse, fixedTop, and pullRight can be
read as props set to true value. When these properties are not specified they
default to false value.
Note that the new App component renders a mix of React Bootstrap custom
components and React native components. Effectively mixing two component
libraries written by different vendors to create your custom app, using a single
component hierarchy. This is the true power of React. Composition is so intuitive that you miss how easy this integration is compared with other solutions.
9

https://facebook.github.io/react/docs/jsx-in-depth.html#namespaced-components

12

Easy Start React

Responsive navigation and call-to-action

Easy Start React

13

index.html template
To change the browser tab title we need to change the page template defined in
the index.html template.
The page template can be used to add webfonts, meta tags, or analytics, just like
you would do in the default HTML file.
You can also use EJS template engine10 syntax to make really powerful templates
for your React apps.
Let us add some EJS to configure meta tags targeting Facebook, Twitter, and
search engines.
<%
var title = 'React Eshop | Easily reusable Eshop in React';
var description = 'Reusable Eshop app in React, complete with a shop\
ping cart, product catalog, and search.';
%>
<title><%= title%></title>
<meta name=twitter:title content="<%= title%>">
<meta property=og:title content="<%= title%>">
<meta name="description" content="<%= description%>">
<meta name=twitter:description content="<%= description%>">
<meta property=og:description content="<%= description%>">
<meta name=twitter:card content=summary />
<meta property="og:site_name" content="React Eshop" />
<meta property=og:type content="website">

In the HTML code for our templates head section we are using EJS tags <%
%> to add JavaScript. The EJS <%= %> tag inserts result of JavaScript expression
following the equal sign within the HTML.

10

http://www.embeddedjs.com/

14

Easy Start React

ESLint for JS guidelines and syntax checking


Using npm start or react-scripts start also runs ESLint on your code. ESLint
checks your JavaScript code against naming conventions, best practices, and
syntactical bugs. Let us try and break one of the rules of ESLint in our app and
see how the error shows up in our Terminal.
Let us replace Component with React.Component in the class definition, without
changing the import statements.
class App extends React.Component {

As you save App.js after making the above change, you will notice the Terminal
starts displaying warnings.
Compiled with warnings.
Warning in ./src/App.js
.../react-eshop/src/App.js
1:17 warning 'Component' is defined but never used

no-unused-vars

1 problem (0 errors, 1 warning)

This warning is somewhat helpful. You can get better insights by integrating
ESLint warnings within your editor.
To integrate ESLint within Atom editor use the following instructions.
Step 1: Add global dependencies required for ESLint editor integration. This
workaround is required until ESLint fix it.
npm install -g eslint babel-eslint eslint-plugin-react
npm install -g eslint-plugin-import
npm install -g eslint-plugin-jsx-a11y eslint-plugin-flowtype

Step 2: Install Linter ESLint Atom package. We also install base linter package
for helpful in-place notifications.

15

Easy Start React

apm install linter-eslint


apm install linter

Step 3: Edit Linter ESLint settings to check Use Global ESLint installation option.
Now as you make coding mistakes which ESLint catches, you will start getting
hints right within your editor. This approach is more intuitive than console
warnings.

ESLint Atom Integration

Easy Start React

16

Flow static type checking


Just like ESLint checking, you can also setup Facebook Flow11 to perform static
type checking within your JavaScript code and avoid really hard to fix bugs.
These may be a category of bugs that are not even picked up by the compiler or
ESLint.
Follow the instructions on Flow GitHub repo12 for installing this useful tool.
Once installed, change to your app root folder /react-eshop and run flow init
just once. This will create a .flowconfig file in your app root. Replace sections
within this file with following code.
[libs]
./node_modules/fbjs/flow/lib
[options]
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
module.name_mapper='^\(.*\)\.css$' -> 'react-scripts/config/flow/css'
module.name_mapper='^\(.*\)\.\(jpg\|png\|gif\|eot\|svg\|ttf\|woff\|wof\
f2\|mp4\|webm\)$' -> 'react-scripts/config/flow/file'
suppress_type=$FlowIssue
suppress_type=$FlowFixMe

Facebook promises they will consider integrating more tightly with Flow in the
future to avoid this workaround.
To help understand the value of Flow let us introduce a type casting bug into our
app.
We can mock a const called version and try to multiply an integer with a string.
render() {
const version = 1 * "beta";

We also display this version within our app title like so.
11
12

https://flowtype.org/
https://github.com/facebook/flow#installing-flow

17

Easy Start React

<h2>React Eshop {version}</h2>

To have Flow examine this file we need to add a // @flow comment at the top of
the file.
Next we go to our Terminal and run npm start command if it is not already running. You will notice that the app runs just fine without any errors or warnings.
However, the title displays NaN where version is supposed to be rendered.
Flow can help us catch this kind of bug. Now open another Terminal window or
stop your app and run flow check command.
src/App.js:8
8:
const version = 1 * "beta";
^^^^^^ string. This type is incompatible \
with
8:
const version = 1 * "beta";
^^^^^^^^^^ number

Found 1 error

If you want to catch such type checking errors in-place within the editor, all you
need to do is install another package. In case of Atom editor we can install the
linter-flow package.
apm install linter-flow

Now if you introduce type checking errors in your code, your Atom editor will
provide in-place Flow notifications. How cool is that!

Flow Atom Integration

Easy Start React

18

Build and deploy


When you are ready to deploy your app, you can use npm run build to generate a
production optimized version of your app.
The scaffold generator performs several optimizations behind the scenes including bundling, minifying, gzip compression, and generating file name hashes
for facilitating browser caching among other things.
Creating an optimized production build...
Compiled successfully.
File sizes after gzip:
build/main.2560bd07.js: 85.42 KB
build/main.327e47d3.css: 21.16 KB
You can now serve them with any static server.

The build folder now contains production ready files. These include glyphicons
as part of Bootstrap, source map files for browser debugging, minified and
gzipped js/css files, and generated index.html from our EJS template.
favicon.ico
glyphicons-halflings-regular.448c34a5.woff2
glyphicons-halflings-regular.89889688.svg
glyphicons-halflings-regular.e18bbf61.ttf
glyphicons-halflings-regular.f4769f9b.eot
glyphicons-halflings-regular.fa277232.woff
index.html
main.2560bd07.js
main.2560bd07.js.map
main.327e47d3.css
main.327e47d3.css.map

You can now use the recommended static server to deploy the build on localhost.

Easy Start React

19

npm install -g pushstate-server


pushstate-server build
open http://localhost:9000

Optionally, you can even deploy your app to Github13 following instructions
provided in the Create React App docs.

13

https://github.com/facebookincubator/create-react-app/blob/master/template/README.
md#deploy-to-github-pages

20

Easy Start React

Deploy using Firebase


You can deploy your app using Firebase static hosting.
To start with you need a gmail account. Login to your gmail account and access
the Firebase website14 . Once you are on their website you will notice Go to
console link on the top right. As you click on this link you will be directed
to create a new project. Once you create a new project you will land on their
administration dashboard with more than 10 feature menus on the left. You are
now active on Firebase free Spark plan.

Firebase Features

To get started with deploying your app, you need to install the Firebase CLI.
npm install -g firebase-tools

Next you need to type firebase login in your Terminal to login to your Gmail
account. Once you are logged in using Terminal, run the firebase init command
within your app root to create the firebase.json configuration file interactively.
Select default options when prompted with exception of following questions.
What do you want to use as your public directory? build
14

http://firebase.google.com/

Easy Start React

21

Configure as a single-page app (rewrite all urls to /index.html)? Y


File build/index.html already exists. Overwrite? N
Once the initialization is complete Firebase CLI will write three new files in your
app root.
database.rules.json contains security rules for your Firebase database
.firebaserc connects the Firebase project with your app
firebase.json sets up rewrite rules and identifies the app directory to
deploy to hosting
Now all you need to do is run npm run build to create a fresh build for your app.
Next run firebase deploy to deploy the build to firebase hosting and you are
done. Hit firebase open on your Terminal to open the Firebase hosted app in
your browser.

Magic Behind Scaffold Generator


You can skip this chapter if you want to continue writing Eshop app without
digging into how Create React App scaffold generator really works.
The Create React App scaffold generator hides a lot of magic behind the scenes
using best of breed tools like Webpack, Babel, PostCSS, and ESLint.
As expert React developer once (if) you reach limits of the app scaffold, you
can run npm run eject and the scaffold generator copies all the dependencies
behind react-scripts into your package.json file. This is a point of no return,
however now you can tweak and customize your development toolchain above
and beyond what Create React App offers.
For most scenarios you may never have to run npm run eject however you may
still be interested to learn what is under the hood. Well in this case read on!

HTML Webpack plugin


The HTML Webpack plugin15 enables generation of index.html for your React
app using a pre-defined template. The index.html file in root of the scaffold
generated app is actually a template. Changes to this template including adding
meta tags, analytics code, and webfonts, will be included in the generated file.
HTML Webpack plugin also enables use of EJS template engine16 syntax to make
really powerful templates for your React apps.

Webpack
One of the most popular and powerful JavaScript module bundler tools, Webpack17 is a versatile tool, however really challenging to configure for beginners.
The scaffold generator manages Webpack development and production server
configurations. It runs production optimized Webpack configuration when you
run npm run build command. You are running Webpack Dev Server behind the
scenes when calling npm start command.
Webpack also helps import and bundle other assets like CSS, JSON, images, and
fonts using specific loaders.
15

https://github.com/ampedandwired/html-webpack-plugin
http://www.embeddedjs.com/
17
https://webpack.github.io/

16

22

Magic Behind Scaffold Generator

23

Hot Reloading
As we save changes to our app, you will notice that the browser tab rendering
our app also refreshes automatically. So while you edit your app in your favorite
code editor, you can keep the browser window side-by-side to view changes
interactively as you change and save each line of code. This magic happens due
to Hot Reloading. Webpack integrates hot reloading for CSS changes within your
app.

Magic Behind Scaffold Generator

24

Babel and ES6


You are able to write your React app using ES6 syntax thanks to Babel18 . Behind
the scenes Babel compiles ES6 to browser compatible ES5 JavaScript. Babel also
handles compiling of JSX to ES5.
Several ES6 features19 are enabled by the scaffold generator.
Arrows. Function shorthand using syntax. Can be statement blocks as well
as expressions returning values.
Classes. Classes support prototype-based inheritance, constructor and super
calls, instance and static methods.
Modules. Enables export and import of JavaScript and React components as
modules.
const and let. The let statement is the new var replacement. The const keyword
is used to define immutable single-assignment values.
Template Strings. Template strings enable passing variables within string
construct and multi-line strings within code editor.
There are many more ES6 features enabled by Babel, which we will discover as
we progress through this book.
Apart from standard ES6 features, the scaffold generator also exposes certain
advanced ES6 and ES7 features.
Syntax trailing function commas. Allows training comma in function parameter definition and call. Generates cleaner git diff when you refactor code to add
more params.
Class property transform. Allows Babel to transform class properties. Enables
properties defined within class definition.
Object rest and spread operators. Enables use of object rest and spread operators for shorthand to pass and consume multiple properties.
Exponentiation operator transform. Transforms let squared = 2 ** 2 statements to represent exponentiation.
React constant elements transform. Read more on how to hoist element
creation20 to the top level for component subtrees that are fully static.

18

https://babeljs.io/
https://github.com/lukehoban/es6features#readme
20
https://babeljs.io/docs/plugins/transform-react-constant-elements/
19

Magic Behind Scaffold Generator

25

PostCSS and Autoprefixer


The scaffold generator enables use of powerful PostCSS21 and Autoprefixer22
plugins to enable addition of vendor prefixes to your CSS among other features.
PostCSS enables you to use next generation CSS features within your app,
without worrying about browser compatibility.

ESLint
Of course as explained earlier, ESLint is integrated within console output as you
run npm start command.
ESLint rules are inspired by Airbnb guidelines23 , however less opinionated. Read
more about the ESLint rules24 supported by the scaffold generator. These rules
relate to possible syntax or logic errors in JavaScript code.
Here is a sample of such rules used by the scaffold generator by default.

disallow assignment operators in conditional expressions


disallow constant expressions in conditions
disallow duplicate keys in object literals
disallow empty block statements
disallow unnecessary boolean casts
disallow irregular whitespace outside of strings and comments
disallow confusing multiline expressions
disallow control flow statements in finally blocks

Other capabilities
The chalk npm package25 adds string styling capabilities within your React apps.
The opn package26 is a better alternative for node-open to open files, websites,
spawn executables from within your apps.
21

http://postcss.org/
https://github.com/postcss/autoprefixer
23
https://github.com/airbnb/javascript
24
http://eslint.org/docs/rules/
25
https://www.npmjs.com/package/chalk
26
https://www.npmjs.com/package/opn
22

Product Summary
Starting this chapter we will build components required for our Eshop. We will
follow bottom-up design for our component hierarchy development, starting
with atomic child component, going up the tree to compose more complex
custom components.
This is what our Eshop will look like at the end of this chapter.

Product Summary component

Eshop feature goals


We will accomplish a lot in this chapter.
26

Product Summary

27

Define product summary schema


Create product summary component
Add to cart button that tracks in-cart quantity
Support for multiple product categories
Featured product view
Product card view
Support for direct and referral distribution

React learning goals


You will learn about custom component design in this chapter.

Data fixtures using JSON


Iteration over list of repeating components
React Bootstrap Thumbnail, Well, Glyphicon, Badge components
Property types and default properties
Event handlers
Component state management
Constructors

Live demo of final completed Eshop is available on ReactEshop.com27 website.


Source for this chapter is available in our GitHub repo28 . Please feel free to
support us by starring this repo.
Get the code. You can clone the source for this entire book, change to app
directory, checkout just the code for this chapter, and start the app to launch
the local version in your default browser.
git clone https://github.com/manavsehgal/react-eshop.git
cd react-eshop
git checkout c03-product-summary
npm start

27
28

https://reacteshop.com
https://github.com/manavsehgal/react-eshop

Product Summary

28

Product summary design


Product summary is our atomic component at the heart of the Eshop app. It
represents the most important call-to-action for converting our Eshop visitors
to informed prospects, referral leads, or paying customers.
We will also reuse this component across at least three important areas of our
Eshop app, the header on home page, the product catalog, and the shopping
cart. So, our design needs to cater to user goals in these three areas of our Eshop.
We also want the categories of our products to be extensible. To start off we will
feature following product categories.
1. Gigs. Users can buy coding gigs. Example: Buy React Webpack starter
project setup for $25.
2. Books. Users can buy books.
Our Eshop will distribute products in two ways.
1. Referral: For products which users can buy on third party stores, we will
redirect to specific stores
2. Direct: Products that users can buy direct from our Eshop
Our minimal product schema supporting product summary can be represented
as follows.

Product Name
Price in US$
Referral (true for referral, false for direct sale)
Referral link (in case referral=true)
Product thumb
Category (Gig | Book)
Featured (if featured display in header)

We want our Product Summary component to be reusable across multiple views.


1. Card view to display in a product catalog
2. Splash view to display featured product on home page
3. List view to display product listing in shopping cart

Product Summary

29

Product summary schema


Let us define our product summary schema. We will start with a JSON fixture
which we will use to mock our app. Later on this fixture will be reused to hydrate
or import straight into our Firebase database.

products.json
There are two important considerations when designing your React data schema.
Unique ids. We are defining a JSON schema with sequence of product ids. This
helps us in two ways. Firebase stores and retrieves this schema optimally. The
ids can also be used as unique keys for iterating React components in a list or
product catalog. This is a React requirement for optimal virtual DOM updates.
Read more at the Facebook reconciliation29 article.
De-normalized. We are also keeping the schema relatively flat or de-normalized (as opposed to hierarchical). This helps in making our JavaScript code referencing the schema more readable. Firebase queries consume fewer resources,
as we could monitor ids to return just the data for that specific id, if that product
changes.
{
"1": { "name": "React Eshop", "category": "Book", "price": 9.99,
"description": "Easily reusable Eshop in React, ES6, and Faceboo\
k.",
"link": "https://leanpub.com/reacteshop", "referral": true, "fea\
tured": true},
"2": { "name": "Setup Webpack React", "category": "Gig", "price": 25,
"description": "Development environment setup using React and We\
bpack."},
"3": { "name": "React Speed Coding", "category": "Book", "price": 9.\
99,
"description": "Develop custom UI library in React, Flexbox, and\
PostCSS.",
"link": "https://leanpub.com/reactspeedcoding", "referral": true\
},
"4": { "name": "Custom React Stack", "category": "Gig", "price": 95,
"description": "Custom React tech stack based on your project."},
29

https://facebook.github.io/react/docs/reconciliation.html

Product Summary

30

"5": { "name": "Custom React Component", "category": "Gig", "price":\


25,
"description": "Custom React component development for your proj\
ect."}
}

You will notice we are missing product thumb in this fixture. We are right now
mocking or rapid prototyping our app so we will make do with placeholder
images to distinguish between a Book and a Gig.
As we design production ready schema in Firebase, we may use Firebase Storage30 to store and retrieve our Eshops media assets in a scalable way.

ProductSummary.js custom component


Let us start coding our first custom component. We will start by importing the
required dependencies for this component.
In the first import statement we are also importing PropTypes to add support
for defining and validating property types. Property validation is tracked during
runtime in the JavaScript console of your browser. You will see console warnings
if any property types are violated in your app. Read more on property types
validation31 in the Facebook documentation.
Next import statement adds the required React Bootstrap components. We will
use Well as container for our product summary card view, Glyphicon to add icons
for buttons, and Badge to convey in-cart quantity of products added by user.
import React, { Component, PropTypes } from 'react';
import { Grid, Row, Col, Well, Thumbnail, Button, Glyphicon, Badge } f\
rom 'react-bootstrap';
import './ProductSummary.css';

Prop validation
Now it is time to define the product summary schema as shape for our component. We do this by defining property type validations and default properties.
30
31

https://firebase.google.com/docs/storage/
https://facebook.github.io/react/docs/reusable-components.html#prop-validation

Product Summary

31

The isRequired flag indicates we expect these properties to be set by the calling
component up the hierarchy. We set sensible defaults for props that are not
required.
Also notice that we are writing a shorthand version of class declaration prefixing export default. Saves a few keystrokes and avoids situations where we miss
adding export default ComponentName at the end of this file.
We are also adding a new prop called display to indicate target view that this
component will render. Depending on the target view our render code will
change, making this component more reusable.
export default class ProductSummary extends Component {
static propTypes = {
name: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
thumb: PropTypes.string.isRequired,
category: PropTypes.string.isRequired,
price: PropTypes.number.isRequired,
display: PropTypes.string.isRequired,
referral: PropTypes.bool,
link: PropTypes.string
}
static defaultProps = {
link: '',
referral: false
}

Constructor, state, and bind


We now add the constructor for our component. Constructor is the place where
we bind event handler methods and define our components state.
Think of your components state as values that will change based on user
actions. These will require a component re-render as the values change. Default
state can be set to a prop passed on by the calling component up the hierarchy.
It can also be set to a sensible default. Like in our case we want to track in-cart
quantity based on how many times user clicks the Add to card button. Our default
for inCart state is zero when the component is first rendered.
Persistent state. As we are in prototyping mode right now, we will not worry
about state that persists over multiple sessions. So, we are not yet handling a

Product Summary

32

scenario where user revisits our Eshop and their in-cart quantities are based on
their actions in the prior session.
We will also require two event handler methods in our component. These methods bind to this context of our component within the constructor definition.
Alternatively we could bind these methods when passing them as props within
render method. However, this is not an optimal solution when compared with
doing so in the constructor. Constructor is only called once in the component
lifetime, however render method may be called multiple times.
constructor(props) {
super(props);
this.state = { inCart: 0 }
this.getProduct = this.getProduct.bind(this);
this.productDetail = this.productDetail.bind(this);
}

Event handler methods


Next we define what our event handler methods do when they are invoked. The
productDetail method is invoked when user clicks on the product thumb or the
info icon button for that product.
The method behaves differently based on product distribution mode. Clicking
referral products simply redirect the user to the referral partner site. Clicking
direct sale products is expected to open up the product detail page within our
Eshop. In prototyping mode we only show a JavaScript alert box to mock product
detail page.
productDetail() {
if (this.props.referral) {
window.open(this.props.link);
} else {
alert(`Mock product detail for ${this.props.name}`);
}
}

The getProduct event handler is invoked when user clicks on Add to cart button
or the Visit site button depending the the product distribution mode. In case
of direct sale products we are incrementing the inCart state by one. This needs
to be done using the React setState method. Calling setState also initiates a rerender of our component UI automatically updating the new value in our Eshop.

Product Summary

33

getProduct() {
if (this.props.referral) {
window.open(this.props.link);
} else {
this.setState({ inCart: this.state.inCart + 1 });
}
}

The render method


Before we dig into the code for our render method, let us understand how we
are developing the render JSX to make the code reusable, readable, and easy to
refactor.
You will remember we want our render method to handle multiple distribution
modes (referral, direct). We want to cater for multiple views for our product
summary (card, splash, list). These variations themselves represent eight permutations for our JSX code.
We also have over-arching usability and conversion goals for our Eshop.
1. Reduce the user cognition load
2. Reduce the number of clicks required for conversion
3. Do more with less features, components, information on the screen

Reverse hierarchy design


Addressing these goals we design our JSX iteratively. Just like we design a
component hierarchy, bottoms-up, from atomic element up. Here is what the
reverse-hierarchy composition of our product summary component may look
like.

Call-to-action button label > is part of >


Get product button > can vary based on distribution mode >
Add to cart (direct) OR Visit site (referral)
Product detail button > only shows up for direct sales mode > is part of >
Product summary buttons group > is part of >
Product thumb > is part of >
Product name > is part of >
Product description > is part of >

Product Summary

34

Product summary card view OR Product summary splash view > is part
of >
* Product summary component
You can think about this design method as specialization and composition if
you come from object oriented design background. The Add to cart or Visit site
variants are specializations for button. Button in turn is composed of button
label and color.
Another way to think about designing your React components is to think of the
hierarchy as container-child-sibling relationship when visually representing
your component UI. So, label is child of button. Button is sibling of thumb. Button and thumb are contained within the product summary container. Thinking
in React32 by Pete Hunt is one of the best articles elaborating this design
method.

Call to action label


So starting from bottom of the hierarchy, we first create the call-to-action
label.
We are expecting different icons based on distribution mode. Globe icon represents a referral link render in user browser. Cart icon represents direct sale
products.
The const keyword indicates immutable declaration, that these values will not
change in the rest of our code. Improves readability of our code, reduces bugs.
We refer to a property passed on to our component using the this.prop.propertyName
syntax.
We also use the JavaScript conditional operator33 which returns JSX after ? for
true condition and JSX prefixed by : for false check.
The behavior we are representing in the callToActionContent JSX is for in-cart
quantity to replace the cart icon when quantity is greater than one.
The callToActionStyle const represents Bootstrap colors for our call-to-action
button based on distribution mode. Blue (primary) for referral, Green (success)
for direct sale.

32

https://facebook.github.io/react/docs/thinking-in-react.html
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Conditional_
Operator
33

Product Summary

35

const callToActionIcon = this.props.referral


? <Glyphicon glyph="globe" />
: <Glyphicon glyph="shopping-cart" />;
const callToActionContent = this.state.inCart
? <Badge>{this.state.inCart}</Badge>
: callToActionIcon;
const callToActionStyle = this.props.referral ? 'primary' : 'success';

Switching variants
We also define the buttonSize based on where we want to display the product
summary component. The use of switch statement makes our component extensible in the future as we may add variations for displaying product summary
in list view in addition to card and splash view.
// Render this for Button bsSize
let buttonSize = '';
switch(this.props.display) {
case 'splash': buttonSize = 'large'; break;
case 'card': buttonSize = 'small'; break;
// Handle future display targets
default: buttonSize = 'small';
}

Get product button


Time to define our get product button. We use the React Bootstrap Button
component to define our button.
The getProduct event handler is attached to our button component for the
onClick event.
We are handling variants for displaying the component in different views, using
the bsSize prop.
Different distribution modes are handled based on this.props.referral conditional expression.
The button is composed of callToActionContent we defined earlier in this section.

Product Summary

36

const getProductButton =
<Button
onClick={this.getProduct}
bsStyle={callToActionStyle}
bsSize={buttonSize}>
{this.props.referral ? 'Visit site ' : 'Add to cart '}
{callToActionContent}
</Button>

Product detail button


Our product detail icon button is designed similar to get product button.
We address variation based on distribution mode. Show product detail button
only for direct sale products.
We complete our JSX for the buttons group by combining the JSX for get product
and product detail buttons.
let productDetailButton =
<Button
onClick={this.productDetail}
bsStyle="default"
bsSize={buttonSize}>
<Glyphicon glyph="info-sign" />
</Button>;
productDetailButton = this.props.referral
? null
: productDetailButton;
const buttons = <p>{getProductButton} {productDetailButton}</p>;

Thumbnail
Let us continue designing product thumb component which is at the same
level in the hierarchy as our buttons. We are using React Bootstrap Thumbnail
component.
We need an href="#" to add hover effect to the thumbnail. The productDetail
method is bound to this component so clicking on the thumb will have the
desired effect. We are setting the image src based on prop passed on from the
owner component.

Product Summary

37

let productThumb =
<Thumbnail
href="#"
onClick={this.productDetail}
src={this.props.thumb}
alt="{this.props.name}" />;

Product summary views


Now it is time to create two views for displaying our component based on the
display prop value.
First we handle the view that enables display of the featured product. This view
will render into a Jumbotron component within the owner component.
This kind of conditional rendering makes our component more extensible and
reusable at the same time. We can reuse product summary in multiple areas of
our app. We can also add new use cases in the future with relative ease.
Grid layout. We are designing this view using React Bootstrap layout components. Grid is a container. Row component stacks vertically. Col component
stacks horizontally. The xs={12} type of props determine the number of child
elements at small (xs), medium (md), and large (lg) responsive breakpoints.
Think of the grid composed horizontally as maximum of 12 elements. The md={6}
prop-value pair indicates that we expect two elements to occupy a horizontal
area when the screen size reaches medium breakpoint. The xs={12} indicates
that one smaller screens we only want one element to occupy the entire horizontal area.
Composition of this view is straight forward. We use const JSX where we
defined these earlier or this.prop.propertyName directly where we render the
value directly.
Note the isolated use of style from ProductSummary.css within className of the
h2 native component. This demonstrates how we can mix our own styles with
React Bootstrap. Do this for React native components you use on your app.
Avoid doing this for React Bootstrap as styles are handled internally within
these components.

Product Summary

38

let renderProductSummary = '';


if (this.props.display === 'splash') {
renderProductSummary =
<Grid>
<Row>
<Col xs={12} md={6}>
{productThumb}
</Col>
<Col xs={12} md={6}>
<h1>{this.props.name} <small>{this.props.category}</small></\
h1>
<p>{this.props.description}</p>
<h2 className="ProductSummary-price">
${this.props.price}
</h2>
{buttons}
</Col>
</Row>
</Grid>;
};

Next we add the second variant for our product summary, to display it as a card
with a product catalog. We use the Well component to draw the container for
our card. Rest of the view design is similar to splash view.
We wrap our product summary component by returning the JSX composed in
the renderProductSummary variable.
if (this.props.display === 'card') {
renderProductSummary =
<Well>
{productThumb}
<h2>{this.props.name} <small>{this.props.category}</small></h2>
<h4 className="ProductSummary-price">
Price: ${this.props.price}
</h4>
<p>{this.props.description}</p>
{buttons}
</Well>;

Product Summary

};
return (renderProductSummary);
}}

Final listing of ProductSummary.js component is as follows.


import
import
from
import

React, { Component, PropTypes } from 'react';


{ Grid, Row, Col, Well, Thumbnail, Button, Glyphicon, Badge }
'react-bootstrap';
'./ProductSummary.css';

export default class ProductSummary extends Component {


static propTypes = {
name: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
thumb: PropTypes.string.isRequired,
category: PropTypes.string.isRequired,
price: PropTypes.number.isRequired,
display: PropTypes.string.isRequired,
referral: PropTypes.bool,
link: PropTypes.string
}
static defaultProps = {
link: '',
referral: false
}
constructor(props) {
super(props);
this.state = { inCart: 0 }
this.getProduct = this.getProduct.bind(this);
this.productDetail = this.productDetail.bind(this);
}
// Product detail button event handler
productDetail() {
if (this.props.referral) {
window.open(this.props.link);
} else {
alert(`Mock product detail for ${this.props.name}`);
}

39

Product Summary

40

}
// Get Product button event handler
getProduct() {
if (this.props.referral) {
window.open(this.props.link);
} else {
this.setState({ inCart: this.state.inCart + 1 });
}
}
render() {
// Different icon depending on Referral or Direct distribution
const callToActionIcon = this.props.referral
? <Glyphicon glyph="globe" />
: <Glyphicon glyph="shopping-cart" />;
// Display inCart quantity if not zero, otherwise display icon
const callToActionContent = this.state.inCart
? <Badge>{this.state.inCart}</Badge>
: callToActionIcon;
// Different color depending on Referral or Direct distribution
const callToActionStyle = this.props.referral ? 'primary' : 'succe\
ss';
// Render this for Button bsSize
let buttonSize = '';
switch(this.props.display) {
case 'splash': buttonSize = 'large'; break;
case 'card': buttonSize = 'small'; break;
// Handle future display targets
default: buttonSize = 'small';
}
// Render this for Get Product button
const getProductButton =
<Button
onClick={this.getProduct}
bsStyle={callToActionStyle}

Product Summary

41

bsSize={buttonSize}>
{this.props.referral ? 'Visit site ' : 'Add to cart '}
{callToActionContent}
</Button>
// Render this for Product detail button
let productDetailButton =
<Button
onClick={this.productDetail}
bsStyle="default"
bsSize={buttonSize}>
<Glyphicon glyph="info-sign" />
</Button>;
// No product detail required for referral products
productDetailButton = this.props.referral
? null
: productDetailButton;
// Render this for call-to-action buttons
const buttons = <p>{getProductButton} {productDetailButton}</p>;
// Render this for product thumb
let productThumb =
<Thumbnail
href="#"
onClick={this.productDetail}
src={this.props.thumb}
alt="{this.props.name}" />;
// What to render as product summary - splash or card (default) st\
yle
let renderProductSummary = '';
if (this.props.display === 'splash') {
renderProductSummary =
<Grid>
<Row>
<Col xs={12} md={6}>

Product Summary

42

{productThumb}
</Col>
<Col xs={12} md={6}>
<h1>{this.props.name} <small>{this.props.category}</smal\
l></h1>
<p>{this.props.description}</p>
<h2 className="ProductSummary-price">
${this.props.price}
</h2>
{buttons}
</Col>
</Row>
</Grid>;
};
if (this.props.display === 'card') {
renderProductSummary =
<Well>
{productThumb}
<h2>{this.props.name} <small>{this.props.category}</small></\
h2>
<h4 className="ProductSummary-price">
Price: ${this.props.price}
</h4>
<p>{this.props.description}</p>
{buttons}
</Well>;
};
return (renderProductSummary);
}
}

App.js product summary render


Now that we have our product summary component ready, it is time to modify
our owner component App.js to render various views.
We will modify the App component to import ProductSummary component.
Our React Bootstrap dependencies do not change since prior version of App
component. We add two placeholder thumb images for book and gig product
categories and import these as well. Importing images this way will bundle

Product Summary

43

them as part of the build we deploy using npm build. The images are not copied
over individually to the build folder. We also import the package.json fixture
data.
We are then assigning a const with parsed JSON string so that the products data
object can be processed by our component JavaScript.
import React, { Component } from 'react';
import ProductSummary from './ProductSummary';
import {
Grid, Row, Col,
Navbar,
Nav,
NavItem,
Jumbotron } from 'react-bootstrap';
import bookThumb from './book-mock.jpg';
import gigThumb from './gig-mock.jpg';
import productsData from './products.json';
const products = JSON.parse(JSON.stringify(productsData));

We will develop the rest of the App component using the same reverse hierarchy
method as we did for product summary component.
We first create the JSX to render the featured product. We are using JavaScript
map() method to map the products objects individual entries to an array of JSX
ProductSummary nodes.
If one of the products is featured, we will set the display prop to splash so
that the appropriate view is rendered. We need to specific key as a unique id as
explained earlier, this is a React requirement for iterating nodes, even if there
is only one in case of featured product.
The rest of the props are set to values from our JSON fixture by referencing these
using JavaScript object notation.
In case of thumb prop, for now as we are creating a mock product catalog, we will
pass the mock images we imported earlier.

Product Summary

44

class App extends Component {


render() {
const productFeatured = Object.keys(products).map(key => {
return (products[key].featured
? <ProductSummary
key={key}
name={products[key].name}
description={products[key].description}
price={products[key].price}
link={products[key].link}
thumb={products[key].category === 'Book' ? bookThumb : g\
igThumb}
category={products[key].category}
referral={products[key].referral ? true : false}
display="splash"
/>
: null
);
});

The product card view is rendered in a similar manner. This time we want to
display multiple product cards nicely laid out like a product catalog mock, which
responsively resizes its contents.
const productCatalog = Object.keys(products).map(key => {
return (!products[key].featured
? <Col xs={12} md={6} lg={3} key={key}>
<ProductSummary
name={products[key].name}
description={products[key].description}
price={products[key].price}
link={products[key].link}
thumb={products[key].category === 'Book' ? bookThumb : gigTh\
umb}
category={products[key].category}
referral={products[key].referral ? true : false}
display="card"
/>
</Col>

Product Summary

45

: null
);
});

Now we are ready to create return the rendered home page view with splash area
featuring our product and product catalog area displaying product cards.
There is no change to the Navbar component JSX. We only replace the Jumbotron children with productFeatured JSX. We also define a Grid for displaying
the productCatalog JSX.
return (
<div>
<Navbar inverse fixedTop>
<Grid>
<Navbar.Header>
<Navbar.Brand>
<a href="/">React Eshop</a>
</Navbar.Brand>
<Navbar.Toggle />
</Navbar.Header>
<Navbar.Collapse>
<Nav pullRight>
<NavItem href="//github.com/manavsehgal/react-eshop">
Code
</NavItem>
<NavItem href="//leanpub.com/reacteshop">
Book
</NavItem>
</Nav>
</Navbar.Collapse>
</Grid>
</Navbar>
<Jumbotron>{productFeatured}</Jumbotron>
<Grid>
<Row>{productCatalog}</Row>
</Grid>
</div>
);
}

Product Summary

46

}
export default App;

Congratulations! That is a lot of learning from one chapter. You just created
your first Eshop ProductSummary component. We also learnt how to rapidly
prototype the product catalog using React Bootstrap, JSX, and React.

Vous aimerez peut-être aussi