This tutorial is out of date and no longer maintained.
Welcome to the fourth and final installment of the Learning React series! Up to this point, we’ve learned how React’s API allows us to create rich stateful components, how to use them in practice & how Facebook’s Flux architecture works.
Today we are going to put all of it together to create a basic shopping cart application. In a typical e-commerce website, the product detail page has several moving parts that rely on one another, and React really helps simplify and organize the co-dependency between them.
If you haven’t already, I highly recommend checking out the first three parts of this series:
During the writing of this series, React released version 0.12 which made some fairly significant changes. This tutorial will be written in 0.12 syntax. Some of the changes include:
/** @jsx React.DOM */
header is no longer required when writing JSX syntaxrenderComponent
is now render
renderComponentToString
is now renderToString
You can check out the entire changelog on the official blog post.
Our first step to architecting an application is defining what it should do. We want to:
This is what our finished product should look like:
This app is going to be purely client-side, so we aren’t going to need a server. Instead, we will be using a mock API and mock data in order to focus on the components themselves. Let’s take a look at our directory structure:
css/
---- app.css
img/
---- scotch-beer.png
js/
---- actions/
-------- FluxCartActions.js // Our app's action creators
---- components/
-------- FluxCart.react.js // Cart Component
-------- FluxCartApp.react.js // Main Controller View
-------- FluxProduct.react.js // Product Component
---- constants/
-------- FluxCartConstants.js // Our app's action constants
---- dispatcher/
-------- AppDispatcher.js // Our app's dispatcher
---- stores/
-------- CartStore.js // Cart Store
-------- ProductStore.js // Product Store
---- utils/
-------- CartAPI.js // Mock API
---- app.js // Main app.js file
---- ProductData.js // Mock Data
index.html
package.json
Below, check out our package.json
file. We will be using the following modules:
We can run npm install
to install all of our dependencies, and then use the npm start
command to start a process that watches our project and bundles our source on save.
In the interests of keeping us focused on Flux and React, we will be using a mock API and mock data for our product that we are going to display. That said, writing our product data and our API similarly to the way we would work with a real API adds the benefit that we could have an easier time plugging in a real API down the road if we had wanted to.
Let’s have a look at what our sample Product Data looks like:
As you can see above, we define a product that has options called variants. Our schema mirrors the type of data you might typically get back from a call to a restful API. We go ahead and load this data into localStorage
, so that our mock API can grab that data and load it into our app.
See below how our mock API grabs our data from localStorage
and then uses Flux actions to send that data to our ProductStore:
So now that we have our sample product data, and a sample API call, how do we use this to bootstrap our application with “server” data?
It’s really as simple as just initializing our data, running our API call, and then mounting our controller view. Our main app.js
file, shown below, is responsible for this process:
Since we are using the Flux architecture for this application, we are going to need to create our own instance of Facebook’s Dispatcher library. We also add a handleAction
helper method to our Dispatcher instance, so that we can identify where this action came from.
While this isn’t explicitly required for our current application, if we wanted to hook into a real API or handle actions from places other than views, it is nice to have this architecture in place if we needed to handle those actions differently than those that originated from views.
In our handleAction
method, we receive an action from an action creator and then have our Dispatcher dispatch the action with a source
property and the action that was supplied as an argument.
Now that we have our dependencies, data, and our Dispatcher set up, it’s time to start working on our project’s functional requirements. Actions are a great place to start. Let’s define some action constants in order to define which actions our app will perform:
After defining our constants, we need to create their corresponding action creator methods. These are methods that we can call from our views/components that will tell our Dispatcher to broadcast the action to our Stores.
The action itself consists of an object containing our desired action constant and a data payload. Our Stores then update and fire change events, which our Controller View listens to in order to know when to begin a state update.
Below, check out how we use our Dispatcher’s handleAction
method to pass an actionType
constant and associated data to our Dispatcher:
Now that we have our Actions defined, it is time to create our Stores. Each Store manages application state for a domain within an application, so we are going to create one for our product and one for our cart. Let’s start with our ProductStore
:
Above, we define two private methods, loadProductData
and setSelected
. We use loadProductData
to, unsurprisingly, load our mock product data into our _product
object. Our setSelected
method is used to set which product variant is currently selected.
We expose this data using the public methods getProduct
and getSelected
, which return their respective internal objects. These methods can be called after require
’ing our Store within a view.
Lastly, we register a callback to our AppDispatcher
that uses a switch statement to determine if the supplied payload matches an action we want to respond to. In the event that it does, we call our private methods with the supplied action data, and fire a change event, forcing our view to retrieve the new state and update its display.
Next up, let’s create our CartStore
:
Above, we laid out our store similarly to the way we laid out our ProductStore
. We use the _products
object to store the products that are currently in our cart, and the _cartVisibility
boolean to define the current visibility status of our cart.
We added some more complex public methods that allow our Controller View to retrieve application state:
getCartItems
- Returns the items in our cartgetCartCount
- Returns a count of how many items are in our cartgetCartTotal
- Returns the total price of all of our cart itemsNow that we have our Stores built, it’s time to get our hands dirty and build out our Views.
Our Controller View is our top-level component that listens for changes on our stores and then updates our application’s state by calling our Store’s public methods. This state is then passed down to child components via props.
The Controller View is responsible for:
We start by creating a public method called getCartState
. We use this method to call public methods on Stores to retrieve their current state and set our applications state with the results. We call this method first during the getInitialState
method, and also when a Store’s change event is received.
In order to receive these change events, we add listeners on our Stores during the mounting process, so that we know when they change. We remove these events when/if our component is unmounted.
In our render
method, we compose our component using the FluxCart
and FluxProduct
components. Here, we pass our state props down to them using component properties, or props.
It’s time to get down to the meat and potatoes of this app, and that is our Product view. We want to take the props that got passed from our Controller View and make a rich, interactive product display.
Let’s get this party started.
Prior to our render
method, we define Action methods that we bind to elements in our component. By importing our Actions, we can then call them from these methods, and kick off the update process:
selectProduct
- Sets which product option is currently selectedaddToCart
- Adds the currently selected product to the cart and opens the cartInside of our render method, we calculate how many units of the selected product are available to sell (ats) by checking how many are in the cart vs the selected products inventory. We use this to toggle the “Add To Cart” button’s state.
Now, we need a cart. So let’s put one together. In our app, when a product is added to the cart, a single line item represents the selected option. The quantity of that option can be increased, but it won’t create new line items. Instead, the quantity being purchased is displayed and the line item’s price adjusts accordingly.
Let’s make this happen:
And now we have a cart! Our cart component has three event methods:
closeCart
- Close the cartopenCart
- Opens the cartremoveFromCart
- Removes item from the cart and closes the cartWhen rendering our cart, we use the map
method to render out our line items. Notice on the <li>
tag, we added the key
attribute. This is a special attribute used when adding dynamic children to a component. It is used internally in React to uniquely identify said children so that they retain the proper order and state during reconciliation. If you remove this and open your console, you can see that React will throw a warning telling you that key
hasn’t been set, and you will likely experience rendering anomalies.
For our cart’s hide and show functionality, all that we are doing is adding and removing an active
class, and letting CSS do the rest.
If you have been following along with the source or building from scratch, hit index.html
and you can see our app in action. Otherwise, check out the link to the demo below. Add items to the cart until inventory depletes to see our buttons’ states change and the totals update in the cart.
Don’t stop there though, take the demo source and try to add some new features to our cart, like a product grid display using react-router or adding more than 1 selectable option per product. The sky’s the limit folks.
This marks the end of the Learning React series, and I hope everybody has had as much fun learning it as I did writing it. I’m of the firm belief that 2015 will be the year of React, so take what you have learned here, get out there and build some cool stuff.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!