With ReactJS becoming one of the hottest JavaScript libraries in late 2014, I decided to investigate the hype. What better way to do so then by building an actual application using ReactJS and the Flux architecture, while documenting my process for others to learn?
Project Directory
To demonstrate and explore ReactJS + Flux, we will build a simple shopping cart application. Below is an overview of what our project structure will look like:
For those interested in simply viewing the finalized project, you may find the source code here.
Environment setup
For this project, we will be using npm to install various packages, as well as gulp for
task automation. Below is the package.json file that contains all dependencies needed for
this project:
Once you have package.json in your root directory, simply run:
Tip: For OS X users, you will have to run sudo npm install.
If you haven’t had exposure to using gulp, use this as your first starting point.
Here, we require the modules we will need within our gulpfile.js. ReactJS supports what is known as JSX, which is a JavaScript-HTML hybrid syntax. We create a gulp task to transform this using reactify for all of our JavaScript files, which will be concatanted into a single file: main.js within the dist folder.
Next up, we also copy our index.html into the dist folder:
We now tell grunt which tasks to load:
When running gulp in our terminal, the above tasks will be executed - our dist folder will
contain our production files which have been converted using reactify.
Lastly, let us configure a watch task which will check for changes in our project files and update the page accordingly:
Running gulp watch will continually check for changes to your project files.
Putting it all together, this is what our gulpfile.js is looking like:
Great! Now we have our environment completely set up. Let’s jump into the application.
Lastly, we create the directories needed within our project.
Index.html
Our first step in building our application will be building our index.html file, which will be
our application entry point.
Here we have included Bootstrap for tables, a div with id=main, which will be where we mount our React components to from main.js. Now that this part is finished, we move onto building our central app.js file.
Main.js and App.js
The purpose of these files is to tie in all of our components that will be needed. For our shopping cart application we will be building two major components: an item catalog as well as an item cart, which will both be defined towards the end of our application.
Introducing Flux
Flux is not to be confused with a framework, it is closer to a design pattern in which we have a
unidirectional data flow throughout our application. This simplifies our logic and allows us to build web applications which will scale over time no matter how complicated and numerous our views and models (from MVC standpoints) may grow.
Over the course of this tutorial, we will cover each of the key components that make up Flux as we develop our application.
Dispatchers
The role of the dispatcher within the Flux model is to prevent race conditions, which it does
by queueing up all events in our application as promises, and will execute them in the order
in which they are received. The first piece of code is actually the boilerplate dispatcher.js
file provided by the Facebook team:
We will need to install es6-promises to use within dispatcher.js. Let’s go ahead and do that:
Note: For OS X users, you will have to run sudo npm install es6-promises.
Note: Keep track of the merge library, which we will use to extend the method
Dispatcher.prototype with additional functionality throughout this project.
With this boilerplate out of the way, we build our own app-dispatcher, which will be responsible for queueing up the incoming actions that our application can take.
Within handleViewAction, we build an object containing the action to take through action: action. Also notice how we used merge to extend the Dispatcher.prototype method from dispatcher.js with this new method.
With our dispatchers now out of the way, we move onto defining these actions which can occur in our application. Before we can do so, let us define some action constants within constants/app-constants.js:
Actions
An Action within the Flux architecture is nothing more than an event which will get propogated through the Dispatcher, which will tell the Store how to react. We will define what a Store
is a little later, but for now let us define all possible actions that can occur in a shopping application:
Here we require the modules we need to define our actions with, and we declare an object AppActions which will contain all our methods that will be executed based on the action.
We first define our addItem method, which takes an item as a parameter. We then call our AppDispatcher.handleViewAction and pass in an object containing the type of action that the Store will need to take, as well as the corresponding item which will be added.
Here we define a similar action to addItem, removeItem which takes an index of the item which we will remove from our Cart which we will define later. Next we define two similar
methods, decreaseItem and increaseItem, which both take an index as the parameter.
Great! We have now defined all the actions that can occur in our shopping application. Let’s check out what our app-actions.js is looking like now:
With our actions defined, we move onto the next main component of our application: the Store.
Stores
Stores in Flux react to events (actions). The Store registers what events it is listening for with the Dispatcher.
Note: Stores look like a controller, but are actually closer to a service
in AngularJS.
We begin with our app-store.js:
Note: We are using the EventEmitter method from NodeJS, which will come in handy when broadcasting that an action has occured to the Store and to the subsequent Components, which we will implement shortly.
Here we define a CHANGE_EVENT variable, which will simply save us some writing in the future. We will use this to signal when an action has taken place and that the Store needs to act accordingly.
Next we will define some dummy items for our actual store (not to be confused with the Store) in the Flux architecture. Since we are not using any sort of external database or API, we will put these items here.
Next up we define the actual methods which handle the data within our cart, which stores the items we have chosen to purchase.
For the most part, these mthods should be self-explanatory. We now implement our actual AppStore, which will merge more functionality into dispatcher.js by extending the NodeJS eventEmitter method.
addChangeListener: This allows the Components to register with the Store, and the Store will listen for changes and will signal the appropriate callback method based on the action that has taken place.
The three methods above are important for registering when an event has been triggered, and we use callback methods in response to these events. Before we do the actual registration with the dispatcher, we will define some methods for getting data from our cart, catalog, and our item totals:
Lastly, we will register events that our Store will listen to with the Dispatcher. To do so, we will perform a switch case based on the action that has been received.
Note: It is important to notice that we return true at the end of our code. Keep in mind that the Dispatcher queues up a chain of promises, and we need to return true in order for these promises to be resolved.
Pro-tip: We provide the Dispatcher with an index in the event that we have multiple stores, we would like to keep track of which Store is trying to register an action with the Dispatcher.
Putting it all together, our app-store.js is looking like this:
Phew. That wraps up the implementation of the Store. Lastly, we move to our Components, which we have alluded to previously.
Components
React Components are essentially our views which grab state from the Store and pass it down through props to the child components. Components update whenever the events occur in our system through the Store via the Dispatcher. Since our view listens to the Store, it knows when the application state has changed and will update accordingly.
With that said, let us build our first React Component: app-addtocart.js:
Here we use React.createClass to create a new Component with two methods: handleClick which will call the addItem action via AppActions and pass the item in to be added to our cart using this.props.item. We also define a render method which will render a button to add a selected item into our cart. Similarly, we define app-removefromcart.js, app-increase.js and app-decrease.js:
Note: It is important to notice that these components do not inherit from a parent, they simply inherit from appActions. This is due to how data flows through the Flux architecture.
With these Components implemented, we now move onto the two big Components: app-catalog.js and app-cart.js, which control and display the items we can purchase and the items we currently want to buy, respectively. We start with app-catalog.js:
We have defined a single method so far: getCatalog which returns a new object containing the set of items within the item catalog.
Next, we define our actual Catalog, which will contain two methods: getInitialState for getting the items within our item catalog, and a render method for displaying these items.
Putting it together, app-catalog.js is shown below:
Lastly, we define our app-cart.js, which handles displaying our cart as well as the items within it.
Like our app-catalog.js, we also define a method for getting the items within our cart through the cartItems method.
Most of the code above should look familiar with the exception of the componentWillMount method. Our Cart Component needs to listen for change events from the catalog. This method will handle this by calling the onChange method, which in turn resets out state to the items we currently have in our Cart, which will in turn get re-rendered via render.
Wrapping up
If you’ve made it this far, congratulations! Go ahead and run gulp in the project root directory and open up dist/index.html. You will be able to add, remove and increase/decrease the quanity of items in your cart.