This post is a part of the React Daily UI post series, a joint effort between Jack Oliver, Sophia Shoemaker, and the rest of the team at Fullstack React. Each day we're explaining in detail how to create a UI component with React.
You can view the Codepen implementation here
Or you view the code on Github here
Welcome to React Daily UI, where we go 100 days building 100 beautiful React applications. We're really excited to be partnering with Jack Oliver who is embarking on this ambitious project with us.
Jack is designing and writing the code for these applications and we're going to deconstruct each one to highlight the features that are unique to React.
Today we're going to explore another form, a checkout form:
Overview
The checkout form for this daily UI is similar to our last form, but it has a few extra goodies we're going to explore today. Here are the topics we're going to cover:
- Javascript Modules in ES6
import
andexport
statements- Using
this.props
andthis.state
to modify data - Stateless Functional Components
- Importing an external library and using it in our code
Table of Contents
- JavaScript Modules
- What is a module?
- Manipulating data with
this.setState()
andthis.props
- Stateless Functional Components
- What is a stateless functional component?
- Using the
pluralize
library - Try it out!
JavaScript Modules
In Jack's CodePen example, all the code for our application is in one file. While this is great for simple applications, as a codebase gets larger, it's important to split the code out into separate files or "modules", for maintainability and readability.
What is a module?
A module is a JavaScript file that contains functions, objects and classes. Although it is just a JavaScript file, there are some differences in the way we use a module compared to how you would normally a JavaScript file being used. We don't necessarily include this file in a
script
tag like we would normally using JavaScript in a web page. Most modules are bundled together using a module bundler such as Webpack, Browserify or RequireJS which bundles all the necessary files and generates a final JavaScript file to be put included in ascript
tag. See this in depth guide on modules and how they are used.
In JavaScript, top level variables are in the global scope, which create challenges. If every part of an application can access and modify data that shouldn't be accessed, unintended consequences can occur and make debugging difficult. Using modules allows us to create variables that are local to the module and do not pollute the global namespace.
There are different implementations of JavaScript modules and methods for using them. Preethi Kasireddy has a great article explaining all the different module implementations.
The latest version of JavaScript, EcmaScript 6 (or ES6, for short) is the first time that the JavaScript language has built-in modules. Since ES6 is the next generation of JavaScript, we're going to use the syntax and techniques for ES6 modules. Let's explore how we use ES6 modules in our code.
import
and export
with ES6
In order to use ES6 modules in JavaScript, there are two important keywords to know and learn: export
and import
. These two keywords allow us to use and reuse code across mutiple files and codebases. Let's first explore what it means to export
something in JavaScript and then we'll learn how to import
those things that we've exported.
export
When we use the export
keyword in JavaScript, we are exposing the variables and functions in a module to anything that imports it (we'll take a look at importing next). We can export any variable (declared with var
, let
or const
), function or class. There are two different ways to export in ES6:
- In our
src/components/CheckoutArea/PaymentFormComponents.js
file we have multiple components we'd like to expose to other modules. The syntax for exporting these multiple components looks like this:
<BasicInput name="name" label="Name on credit card" type="text" placeholder="John Smith" />
By wrapping the function names in curly braces, we are telling our environment we want this collection of items to be available to any module that imports it.
- Another approach is to import just a single value. If we want to do this, we'll need to add the
default
keyword to our export:
export default Checkout
This tells the environment that the object that should be exported from the module is the Checkout
variable and it's associated data is available without needing to specify the exact names of properties we are exporting.
import
Now that we know how to export
, let's look at how to use this information in another module. This is where the import
keyword comes in.
There are a few different ways to import
a module. We'll look at each different variation, explain how it works and show an example in our codebase of how it's used.
- The most common way to
import
another module is to import the default export of a module. We see this frequently used in React codebases like this:
import React from 'react';
- Another way to
import
another module is to use the curly braces (similar to how we used them for exporting):
import { PaymentForm } from './PaymentFormComponents'
The curly braces around the name of the component we are importing indicates that we want to import that explicitly named export. This is different than the previous example where we were importing the default export.
- One other way to
import
a module is by using*
. This says we want to import all the exported values from a module. It is not recommended unless you know exactly what a module contains:
import * as Workspace from './WorkspaceComponents'
In our form from the previous post, we had all of our code in one file. Since our checkout form has significantly more components, we are going to split our code out into several folders and files and use the import
and export
keywords we just learned.
Here's a snapshot of our file structure in the Github repository:
It's helpful to split out components by areas of functionality. Our checkout form has three distinct areas and within those three areas our code is broken down into several components:
In the upper left-hand corner we have a slider (the purple rectangle) which controls the number of days a user can rent the co-working space. In the main section of our application on the left-hand side (the red rectangle), we have an image and some text describing the space (the two blue rectangles). On the right-hand side (the dark purple rectangle) we have a breakdown of the cost, which we will call the "order summary" (the green rectangle). The data in this section changes when we move the slider in the upper left-hand corner. The right-hand side also has the form where the user submits payment information (the brown rectangle).
Let's take a look at the code for a few of our files:
src/App.js
Our App.js file is the main file for our application. This is the parent component that renders all the child components for our application.
At the top of the App.js
file, let's import two files we'll use which contain the logic for the left and right hand side of the main part of our application, the ImagePreview.js
and Checkout.js
files using the ES6 import
:
import ImagePreview from './components/ImagePreviewArea/ImagePreview'
import Checkout from './components/CheckoutArea/Checkout'
In the render function of our App.js
file, we'll use two child components:
- An
Overlay
component which contains the background image for our application
overlay = (
<Overlay image="https://s3-us-west-2.amazonaws.com/s.cdpn.io/557257/jj-2.jpg" />
);
- A
Container
component which contains the two imported child components --ImagePreview
andCheckout
.
container = (
<Container>
<ImagePreview price={this.state.price} duration={this.state.duration} people={this.state.people} image="https://s3-us-west-2.amazonaws.com/s.cdpn.io/557257/jj-2.jpg" />
<Checkout duration={this.state.duration} discount={this.state.discount} tax={this.state.tax} price={this.state.price} onSubmit={this.handleSubmit} />
</Container>
);
These child components are wrapped in the ReactCSSTransitionsGroup
components:
<div className="App">
<ReactCSSTransitionGroup transitionName="overlay" transitionEnterTimeout={500} transitionLeaveTimeout={300}>
{overlay}
</ReactCSSTransitionGroup>
<ReactCSSTransitionGroup transitionName="container" transitionEnterTimeout={500} transitionLeaveTimeout={300}>
{container}
</ReactCSSTransitionGroup>
If you'd like to learn more about how those components work, you can read the section from our previous post on how we use ReactCSSTransitionGroup
with our components.
We'll also use the import
statement in our two child components ImagePreview
and Checkout
.
src/components/ImagePreviewArea/ImagePreview.js
In the ImagePreview.js
file, we'll import everything that is exported from the file in src/components/ImagePreviewArea/WorkspaceComponents.js
and refer to the object as Workspace
:
import React from 'react'
import * as Workspace from './WorkspaceComponents'
When we want to use an object, function or component from the WorkspaceComponents.js
file, we'll use it with the dot (.
) operator, like this:
<Workspace.Information name="Coworking Space, South Korea" price={this.props.price} duration="1" />
<Workspace.Meta people={this.props.people} />
Finally, the last line of code in our ImagePreview.js
file is the line where we export
the variables from our file. In this case, we are exporting the ImagePreview
variable which is a React component:
export default ImagePreview
src/components/CheckoutArea/Checkout.js
In our Checkout.js
file we are importing two files, OrderSummary.js
and PaymentForm.js
:
import OrderSummary from './OrderSummary'
import { PaymentForm } from './PaymentFormComponents'
The PaymentForm.js
file contains the form we need for the payment form and the OrderSummary.js
file contains the cost breakdown of our order. We'll discuss how the data for this file changes in the next section.
Manipulating data with this.setState()
and this.props
In our checkout application we have a slider in the upper left hand corner of our application which modifies data in the order summary section. When a user moves the slider, the number of days in the order summary section changes.
In order for the days value to change, we need to change the state of our application. React components have a state
property which we discussed in our previous post.
We define the initial state values of the component's data by defining the getInitialState()
method of a React component. This method returns an object that contains the initial state of our application. One item in our state
object, the duration
value is the data we are going to use to display and modify how many days a user has chosen. Let's return the initial value of this duration
variable as 5
.
getInitialState: function() {
return ({
mounted: false,
people: 1,
price: 320.00,
tax: 20,
duration: 5,
discount: 5
});
},
Using our component's state
coupled with a child component's props
we can send data down to our child components and update the number of days displayed in the order summary section.
A child component does not pass any data back up to it's parent component. This means we can only ever pass data down the component tree. This pattern of data passing is called one-way databinding.
A child component can inform the parent component about an update it makes, but cannot change the data itself. In order to inform the parent component that a change has happened, we'll need pass down a function as a prop.
The difference between props
and state
can be somewhat confusing. props
are used to pass down data and event handlers from parent components to child components. state
on the other hand is used to manipulate the current state of a component.
In our src/App.js
file, we've defined a <Header>
component which contains our input slider. We create an instance of this component as a child of our main App
component:
<Header onChange={this.handleChange} />
This <Header>
component has a property called onChange
. The value of this property is the handleChange
function in our src/App.js
file.
Our input
element is defined in the render
function of our Header
component:
var Header = React.createClass({
render: function() {
return (
<header>
<input onChange={this.props.onChange} type="range" max="100" min="1" step="1" />
</header>
);
}
});
When a user moves the slider, the onChange event is triggered for the input element and the callback function we've passed down as a prop
-- this.props.onChange
is called.
The code for this callback function lives in our App
component (in src/App.js
) and contains a call to this.setState
. This function call causes the state of our application to change. In this particular call, we are changing the value of duration
to be the value from the slider input:
handleChange: function(e) {
this.setState({ duration: e.target.value });
},
In order to pass down the duration
value when the user changes the slider, we're going to send the data to our Checkout
component via props
, similar to how we passed down a function via props
with our Header
component. Our Checkout
component has a property called duration
and we give it the value contained in our App
component's this.state.duration
.
container = (
<Container>
<ImagePreview price={this.state.price} duration={this.state.duration} people={this.state.people} image="https://s3-us-west-2.amazonaws.com/s.cdpn.io/557257/jj-2.jpg" />
<Checkout duration={this.state.duration} discount={this.state.discount} tax={this.state.tax} price={this.state.price} onSubmit={this.handleSubmit} />
</Container>
);
Our src/components/CheckoutArea/Checkout.js
file has an OrderSummary
component that also has a property called duration
. We will pass our Checkout
component's value found in this.props.duration
to the OrderSummary
component's duration
property where it will get used to display the number of days a user selects and calculate the final amount based on the cost per day.
render: function() {
return (
<div className="Checkout">
<OrderSummary duration={this.props.duration} discount={this.props.discount} tax={this.props.tax} price={this.props.price} />
<PaymentForm onSubmit={this.props.onSubmit} />
</div>
);
}
Stateless Functional Components
Some of our components only change when the parent component gives it new data. The component itself doesn't require the use of this.state
to make modifications to it's own state. When this is the case it's beneficial to use stateless functional components.
What is a stateless functional component?
A stateless function component is a component that does not have certain attributes of React components. Three main features distinguish these components:
It does not have a backing instance which means it does not maintain a reference to an actual DOM node
It does not have lifecycle methods
It does not have an internal state (no references to
this.state
or usingthis.setState
)See the React docs on stateless functional components for more information.
Using stateless functional components will allow the React algorithm in the future to optimize rendering and memory allocation.
A stateless functional component does not make use of the React.createClass
method that is commonly used to create a component. A stateless functional component is just a regular JavaScript function that has a props
as a parameter. We'll uses these props
just like we would use them in a regular component. React expects us to return a JSX component value from this function, just like what we'd return from a component's render
function.
Put another way, the stateless function component is useful when a component just has a render
function and doesn't need to compute or modify any data.
Let's take a look at an example from the code that uses this pattern:
function Information(props){
var duration = pluralize('day',props.duration);
return (
<div className="WorkspaceInformation">
<div className="WorkspaceName">{props.name}</div>
<div className="WorkspacePrice">
<div className="Price">{props.price} GBP</div>
<div className="Duration">/ {duration}</div>
</div>
</div>
);
}
This Information
function has a props
parameter and we use that props object to display information coming from our parent component. Looking through our modules, you'll find most of our components can be made into stateless functional components, since most of them meet the criteria for stateless functional components.
Using the pluralize
library
There are multiple places in our code where we need to display the plural form of a word. Instead of writing the same code in multiple places, or even creating a function that we can use, there is a library -- pluralize -- that will modify our text to have the plural form if we need to. This library has done the heavy lifting for us of determining how to pluralize a word. We can leverage plurarlize in our application.
In order to use pluralize we'll need to first install it via npm
:
npm install pluralize --save
Then we can use the function pluralize provides in our application:
function Information(props){
var duration = pluralize('day',props.duration);
return (
<div className="WorkspaceInformation">
<div className="WorkspaceName">{props.name}</div>
<div className="WorkspacePrice">
<div className="Price">{props.price} GBP</div>
<div className="Duration">/ {duration}</div>
</div>
</div>
);
}
Try it out!
Check out the Codepen example:
The complete source for this article is also available on Github here.
To start the app, download the code,
cd
into the project directory and type:npm install npm start