A beginner's guide to 7 Eleven CliQQ app

The README.md gives you adequate information on how to clone 7 Eleven CliQQ files, install dependencies and launch the example app. Once you have been successful at that, you might be interested in how things work under the hood. You might have come across this while reading README.md-

Tech Stack

Here's a curated list of packages (in no particular order) that you should have in-depth knowledge of, before starting with this project. However, the best way to have a complete list of dependencies is to see package.json.

Core

Unit Testing

Linting

There are 80+ node packages in package.json. If you're willing to go that extra mile, we'd suggest you do a google on each and every package and find out how it works and fits in this boilerplate. That will give you a sense of how even the smallest of things work.

Note that several features are optional and there are instructions in the docs on how to remove them.

Project Structure

Let's start with understanding why this project has chosen this particular structure.

  • Your source code will reside in the app folder.
  • Configuration, Generator and Template files reside in the internals folder.
  • The files in mocks are maintained autmatically by Jest.
  • The server folder contains development and production server configuration files.

app/

We use container/component architecture. containers/ contains React components which are connected to the redux store. components/ contains dumb React components which depend on containers for data. Generally, you'd want to treat one webpage (such as Login, Home etc.) as one container and a small part (such as Login form, Navigation bar) of that page as a component. But there are no rigid rules. You can bend this structure to your needs.

internals/

You can call this area the build engine of your app/. Your source code cannot be executed as-is in the web browser. It needs to pass through webpack to get converted into a form that web browsers understand.

internals/webpack: You'll most probably use EcmaScript 6 or EcmaScript 7 to write the source code of your app. EcmaScript is the standard for JavaScript. Most people are still using browsers which understand EcmaScript 5. So your code must be transpiled into browser-understandable code. To apply the transpiler to your source code, you will use webpack. Feeling the jitters already? Don't worry. Take a tea-break and then read on.

internals/generators: This folder has the code to scaffold out new components, containers and routes. Read more about scaffolding in the docs.

There are other folders there too, but for brevity let's just skip them.

server

As the name suggests, this folder contains development and production server configuration.

Basic Building Blocks

These days when musicians produce music, they record different parts of the song separately. So vocals, drums, guitar, bass may be played in separate sessions and when they're satisfied with their work, the sessions are combined into a beautiful song. In a similar fashion, let's understand the role of different technologies and in the end, we'll see how everything converges into a single application.

You can launch the example app by running npm start. To fully understand its workings, you'll have to understand multiple technologies and how they interact. From this point, we're going into an overdrive of implementation details. We'll simplify the technical jargon as much as we can. So please bear with us.

How does the application boot up?

Your single page application hooks into app/index.html. That means React will render your components inside a div which is injected into index.html. But how do we include all of our react components into a single html? That's where webpack comes into the picture. Webpack will literally pack your application into small javascript chunks. These files will be included in a <script> tag which is injected into index.html . When your application is deployed on a server, browsers will load this html file. The javascript files that webpack has included will be executed by the browser, thereby booting up your react application! It's magic really!! No, not really, though is certainly can seem that way. Let's dissect this phenomenon to better know what's really going on.

app/app.js:

When you run npm start, a webpack development server will be launched inside your terminal. You can then start browsing at http://localhost:3000.

Before launching the server, Webpack requires an entry point to your application. Think of it as a door to your source code. In this boilerplate app/app.js will be that entry point. Webpack will access the entire app from this file, transpile the application into ES5 and will create small chunks of transpiled code. Only the required chunks will be loaded in the browser so that you won't have to worry about the size of your application.

app/app.js could be one of the most confusing files for any beginner. It contains all the things that are globally applied to your app. You'll see that there's a lot going on. Let's break it down.

  • babel-polyfill This will emulate a full ES2015 environment which in turn enables cool stuff like generator functions, Promises, etc.
  • A redux store is instantiated.
  • A history object is created which remembers all the browsing history of your app. (BTW, very useful for analytics)
  • A Router is set up, with all of your routes. See routes.js
  • Hot module replacement setup.
  • i18n internationalization support setup.
  • ReactDOM.render() not only renders the root react component called <App />, of your application, but it renders it with <Provider />, <LanguageProvider /> and <Router />.
  • <Provider /> connects your app with the redux store.
  • <LanguageProvider /> provides language translation support to your app.
  • <Router /> will have information for your application routes.

React Router:

<Router /> has the crucial information for your routes. Check out routes.js to see how route paths are mapped with application containers.

  • Path "/" corresponds to container <HomePage />
  • Path "/features" corresponds to container <FeaturePage />
  • Path "*" i.e. all other paths correspond to the <NotFoundPage />

These containers along with their corresponding reducer and sagas are loaded asynchronously with the help of dynamic import(). Whenever webpack encounters import() in the code, it creates a separate chunk for those imports. That means for every route, there will be a separate chunk. And by corollary, only those javascript chunks will be downloaded by the browser which are required for the current route. So when you navigate to "/", only chunks related to <HomePage /> will be downloaded and subsequently executed. This makes your application incredibly lightweight and lightning fast.

Redux:

Redux is going to play a huge role in your application. If you're new to redux, we'd strongly suggest you to complete this checklist and then come back-

  • [ ] Understand the motivation behind redux
  • [ ] Understand the three principles of redux
  • [ ] Understand and rewrite ALL example apps from their repo on your own.

Redux store is the heart of your application. Check out store.js to see how we have configured the store.

The store is created with the createStore() factory, which accepts three parameters.

  1. Root reducer: A master reducer combining all reducers.
  2. Initial state: You can initialize your state beforehand.
  3. Middleware/enhancers: Middleware are third party libraries which intercept each redux action dispatched to the redux store and do stuff. For example, if you install redux-logger as a middleware, it will listen to all the actions being dispatched to the store and print previous and next state in the javascript console. It's particulary helpful for developers to track application activity.

In our application we are using two such middleware.

  1. Router middleware: Keeps your routes in sync with the redux store.
  2. Redux saga: Used for managing side-effects such as dispatching actions asynchronously or accessing browser data.

Reselect:

Reselect is a library used for slicing your redux state and providing only the relevant sub-tree to a react component. It has three key features-

  1. Computational power
  2. Memoization
  3. Composability

Imagine an application that shows a list of users. Its redux state tree stores an array of usernames with signatures -

{id: int, username: string, gender: string, age: number }.

Let's see how the three features of reselect help.

  • Computation: While performing a search operation, reselect will filter the original array and return only matching usernames. Redux state does not have to store a separate array of filtered usernames.
  • Memoization: A selector will not compute a new result unless one of its arguments change. That means, if you are repeating the same search key, reselect will not filter the array again and again. It will just return the previous result, like caching. Reselect compares old and new arguments and decides whether to compute a new result.
  • Composability: You can combine multiple selectors. For example, one selector can filter usernames according to a search key and another selector can filter the already filtered array according to gender. One more selector can further filter according to age. You combine these selectors by using createSelector()

Redux Saga:

If your application is going to interact with some back-end application for data, we recommend using redux saga for side effect management. Too much of jargon? Let's simplify.

Imagine that your application is fetching data in json format from a back-end. For every API call, ideally you should define at least three kinds of action creators:

  1. API_REQUEST: Upon dispatching this, your application should show a spinner to let the user know that something's happening.
  2. API_SUCCESS: Upon dispatching this, your application should show the data to the user.
  3. API_FAILURE: Upon dispatching this, your application should show an error message to the user.

And this is only for one API call. In a real-world scenario, one page of your application could be making tens of API calls. How do we manage all of them effectively? This essentially boils down to controlling the flow of your application. What if there was a background process that handles multiple actions simultaneously, communicates with redux store and react containers at the same time? This is where redux-saga comes into the picture. Redux-saga docs are so well-written that we could not put it better than this-

The mental model is that a saga is like a separate thread in your application that's solely responsible for side effects. redux-saga is a redux middleware, which means this thread can be started, paused and cancelled from the main application with normal redux actions, it has access to the full redux application state and it can dispatch redux actions as well.

Example App: Behind the scenes

The react-boilerplate building blocks interoperate to produce a seamless application. Let's join these pieces together.

boilerplate workflow

<HomePage />

Run npm start to launch the application. If you start browsing at https://localhost:3000, by default you will be navigated to the home page. Here, notice that route is "/", so the <HomePage /> container will be mounted. It is responsible for rendering a <form /> with a textbox and a <List /> of repositories.

  • mapDispatchToProps(): Generally, we provide outgoing action creators (functions that create action objects) to the react component through this method. Notice that for every keypress in textbox, your state will be updated by dispatching a changeUsername action to the store. So at any point in time, your redux state will hold the currently typed username. When you submit the form, another action, loadRepos will be dispatched.

  • mapStateToProps(): Generally, we provide incoming state from redux store to the react component through this method. Notice that the we do not provide the entire state to the component, simply because we don't want the react component to have access to irrelevant data. The state will be filtered by selectors such as selectRepos, selectUsername etc.

Together these two methods work like magic. When you type something in the textbox the following things will happen in a sequential manner-

  1. changeUsername() will send text to the redux store. The text can be accessed using evt.target.value. Here, evt is the onChange event emmited by pressing a key.
  2. Redux store will consult with its corresponding reducer, since a reducer knows what to with the data.
  3. When a reducer computes a new state tree, the store will update its state with the newly typed data.
  4. An update has occured in the state, therefore mapStateToProps() will be triggered and your react component will get the new data.
  5. The updated data will be set as the value to your <Input />.

So you see, if you type something in the textbox, it will not be directly reflected in the DOM. It must pass through redux. Redux will update the state and return it to the component. It's the component's responsibility to show the updated data.

HomePage/sagas.js

You must be wondering where does the list of repositories come from! Sagas are primarily used for making API calls. Sagas intercept actions dispatched to the redux store. That means a saga will listen to the actions and if it finds an action of interest, it will do something.

Sagas are nothing but ES6 generator functions. These functions act as normal functions, the only difference is that they can be "paused" and "resumed" at any point in time. Redux saga provides an intuitive API for managing asynchronous operations.

Check out ProfilePage/sagas.js. It can be confusing for untrained eyes. But the API of redux-saga is self-descriptive.

  • You can fork a saga to send it to the background. That way, your code will not get blocked even when the saga is continuously running.
  • takeLatest is used for listening for a particular action. In this case, it will wait for a LOAD_REPOS action. Whenever you disptach this action, the saga will understand that you want to fetch repos from github's public API by calling getRepos().
  • If the API successfully returns some data, a reposLoaded() action will be dispatched which carries the data. When redux store receives this action, a reducer will set incoming data in the new state tree.

An update has occurred! mapStateToProps() will be triggered. <HomePage /> will receive the new data. It's now the container's responsibility to properly display the list of repositories.