With the great prevalence of web applications and companies transforming from traditional desktop-based systems to web-based systems, there are now a multitude of opportunities on the World Wide Web. There are various programming languages for server-side scripting (SSR), client-side scripting, presentation logic (HTML and CSS), and query languages. JavaScript is one of the most popular languages on the web, and it encompasses several frameworks that assist in its development, compilation, and production. React is one of the most popular JavaScript frameworks, and it is developed and distributed by Facebook. While React helps in building highly sophisticated, interactive user interfaces, Redux, on the other hand, is getting very popular in the frontend community for state management. In this chapter, we will get you familiarized with React, the concept of functional programming, the major components of Redux, and how to get started with it.
We will discuss the following topics in this chapter:
- The need for Redux
- The concept of functional programming
- The components of Redux
- Getting started with Redux
- Setting up the project
The amalgamation of React and Redux is trending over the internet, but this popularity should not be a reason for using Redux in your application. Instead, you should be asking why you need Redux. What problems does it solve? A lot of technical books and blogs claim that Redux facilitates state management. That statement, in itself, is very vague. This is even vaguer a claim given that React also has state management. So, why should we use Redux in our applications?
React has a unidirectional data flow. The data is passed to a lower component by using props
. For example, consider a simple state machine, as shown in the following screenshot:
![](https://static.packt-cdn.com/products/9781789610086/graphics/1185ffdb-c5e4-43da-840c-a7d5794904e0.png)
The main component, App
, holds the state of the machine and the props
. The state status is passed down as the props
, as follows:
![](https://static.packt-cdn.com/products/9781789610086/graphics/8c4acf90-708c-47a7-80c8-fb78839eab8c.png)
In order to change the data up to the tree, a callback function must be passed as the props
to any component that changes the state:
![](https://static.packt-cdn.com/products/9781789610086/graphics/8a42aaf3-f3ca-47a6-b43b-161ee62cfc3d.png)
This is a normal scenario in any React application. When you keep building the application, more and more components are aggregated. Your application will react to the state; you will have layers of components, and the top layer will pass props
to the child components. To understand this scenario, let's look at the example of Pinterest:
![](https://static.packt-cdn.com/products/9781789610086/graphics/6bc6ad5b-d1da-424d-afc8-d20a4be4196c.png)
To achieve the layout shown in the preceding screenshot, we will require several components, and each component will need to pass props and states to its child components. A mocked-up version for different components would look something like the following snippet:
<App state={user: {}}> <Navbar user = {user}> <Logo /> <SearchBar /> <Menu> ... </Menu> </Navbar> <Content user={user}> <TitleBar user={user} /> <Avatar user={user} /> <Board data={data} /> .... </Content> <Footer /> </App>
In the preceding snippet, it is obvious that some props and states are used multiple times. For example, the Avatar
component, the TitleBar
component, and the Navbar
component require user information. In order to deliver user information, each of the parent components must pass the props to their child components. It is possible to achieve this by passing the props; however, it will be cumbersome and painful if we have a bunch of components working together. By now it must be obvious that, when we work with React, we are dealing with a lot of components interacting with each other.
Instead of an intermediate component accepting and passing along the props, it would be nice if the component did not need to know about the data. This is the problem that is solved by Redux. It provides direct access to the required dataset.
It is very intimidating to start coding your application. However, you can avoid a lot of hassle and debugging time if you can model your application. There are several tools that are available to model your application. If your model looks like the one in the preceding example, you can consider using Redux. If you feel the need to cache data between views and remember the data for the next layer, Redux is the best option. Finally, if you know that your application is large and your web application will deal with a large set of data that will fluctuate over time, Redux is a good option; it will help you to build an abstraction between the physical layer and the data layer.
Frequently asked questions
The following is a list of frequently asked questions about Redux:
- Can I use Redux without React?Yes; Redux is an elegant library for state management. It can be used with any other library, including Vanilla JS, Angular, Vue JS, JQuery, Ember, Aurelia, and others.
- Do I need Redux to build React applications? No; Redux facilitates managing the data layer in React applications. It really depends on the type of application that you are building. The amalgamation of React with Redux is very popular on the web, but you should really think about whether you need Redux. React already does state management, so using Redux for smaller applications will be overkill.
- What do I need to use Redux? You will need ES6, ES5, or later versions.
A lot of blogs, books, online tutorials, videos, and courses found on the World Wide Web start with a common statement, saying that Redux was built on the top of functional programming. The statement is valid, which means that developers like us need to understand the concept of functional programming.
Let's point out some of the important characteristics of functional programming, as follows:
- Functions are first class objects
- Functions can be chained together
- Functions can be passed as arguments
- Functions, recursions, and an array can be used to control the flow
- We can use pure, higher-order, closure, and anonymous functions
- We can utilize several helper functions, including
map
,filter
, andreduce
In functional programming, functions are considered first class citizens. This means that the language does support passing functions to other functions as arguments, and returning them as the values for other functions. Moreover, they can also be assigned to other variables, or stored in some data structure.
An example of calculating body mass index (BMI), provided the height (in meters) and weight (in kilograms), can be created via the following method. The function is stored in a variable named bmi
and can be called whenever it is required:
const bmi = (weight, height) => weight / (height * height);
A variable can be added to any object. Since a function is stored in a variable, it can also be added to objects, as follows:
const myCalculator = { bmi: (weight, height) => weight / (height * height) };
Similarly, we can add it to an array, as follows:
const myCalculator = [ Bmi => (weight, height) => weight / (height * height) ];
Functions can be used as arguments for other functions. Let's use the preceding bmi
function to check whether a person has an obesity issue. According to the BMI scale, someone with a bmi
between 30.0
and 54
is said to have obesity. We will pass a function as an argument, as follows:
const bmi = (weight, height) => weight / (height * height); const hasObesity = (bmi) => bmi >= 30.0 && bmi <=54; console.log(hasObesity(bmi(100, 2.2)));
Another common scenario is when a function returns another function, as follows:
const bmi = (weight, height) => weight / (height * height); const calculator = () => { return bmi; };
Higher-order functions (HOF) is the fanciest term you will be hearing when getting started with functional programming. Higher-order functions are functions that take functions as arguments or return functions. By now, we have already been consuming such functions. Remember Array.reduce()
, Array.filter()
, and Array.map()
? These are all higher-order functions. In the Redux library, we are consuming some of the HOF, too (such as connect()
).
The most common definition of a pure function is a function that does not have side effects. This is to say that the returned value of a pure function is not affected, influenced, or changed by anything other than its input parameters. Provided the same input, the pure function always generates the same output. An example is as follows:
const sum = (a, b) => a + b;
This is an example of a pure function. Assuming that you call the function sum(6,9)
, the result is always 15
, irrespective of the number of times that you run the function. You can be confident, if you are calling a pure function with the same input, that you are always going to get the same output, which means that the output is predictable. An example of an impure function is the following square
function. In addition to returning the square of the number, the function might be updating the number in a database:
function square(number) { updateNumberInDB(number); return number * number; }
A composition is a very important concept of functional programming; it is how we create a higher-order function by consuming and combining simpler functions.
Let's just use the sum
function that we defined previously. We can split the sum
function into the following composition:
const sum = a => b => a + b sum(2)(5)
Any function can be transformed into a composable function via the concept of currying. Explaining these fundamental functional concepts is beyond the scope of this book, and we suggest that you get familiar with functional terms as much as possible, in order to get a full understanding of Redux and React. We will consume a composition function from Redux, called compose
, in upcoming chapters.
Redux makes it possible to store all statuses in an application in a single place, which is called a store. A store is the intermediary to all of the changes of the status of the app. Using Redux, a component cannot communicate directly with another component; instead, the changes always go through a single source, which is an action. Redux can be described simply by three fundamental principles. Those three main principles, which will be briefly explained in this chapter, are summarized as follows:
- A single source of truth
- The read-only nature of the state
- The reducer principle
The whole application state is stored in a single object, called the state tree. This makes it easier to create modern applications, as the server state can easily be serialized and hydrated to client apps.
An example for an online medical store app is as follows:
![](https://static.packt-cdn.com/products/9781789610086/graphics/9222144e-9237-439c-a897-b33b5a10aae3.png)
Emitting an action is the only way to change the state of a Redux app. Views cannot directly write to the state tree. In Redux, every intent needs to dispatch actions, which tell the reducers (a function) to transform the state. Mutating the state is also not recommended; hence, every time, the reducers write the existing state object with the new version:
![](https://static.packt-cdn.com/products/9781789610086/graphics/6546d388-1cb4-42a2-b5e0-a42bb926608d.png)
When we talk about Redux, we usually include other libraries that work together in harmony. Here, we will discuss some of the important libraries that work well together, as follows:
react-redux
: This allows us to communicate in both directions, between React and Redux (https://github.com/reactjs/react-redux). It is a binding between React and Redux that allows us to create containers and listen to the store changes, reflecting that into a presentational component. We will explore more about container components (smart components) and presentational components (dumb components) in upcoming chapters.redux-devtools
: This is the official implementation of developer tools for Redux, and it allows for watching state changes, live to edit actions, time traveling, and more (https://github.com/gaearon/redux-devtools).redux-promise
: This is middleware for Redux, allowing you to dispatch JavaScript promises to the Redux store (https://github.com/acdlite/redux-promise).
redux-auth
: This library allows you to easily integrate token-based authentication into your Redux application (https://github.com/lynndylanhurley/redux-auth).
redux-logger
: This is middleware to log Redux actions and state changes in the console (https://github.com/evgenyrodionov/redux-logger).
An official overview of the Redux ecosystem can be found on the Redux website, at http://redux.js.org/docs/introduction/Ecosystem.html.
There is a community-maintained repository called awesome Redux. This repository contains resources, libraries, utilities, boilerplate code, and examples associated with Redux, and is located at https://github.com/xgrommx/awesome-redux.
To understand Redux, we need to understand its components. There are four main elements of Redux; let's discuss each of them, one by one.
Actions are simply JavaScript objects describing the changes in the state of the application. To be specific, they are payloads of information that transfer data from our application to the state. Does this not make sense to you? No problem. Let's look at an example use case. Suppose that we need to add a doctor's information to our hospital management system:
const ADD_NEW_DOCTOR_REQUEST = "ADD_NEW_DOCTOR_REQUEST"
It isn't rocket science, right? It's just a simple, constant ADD_DOCTOR_REQUEST
. Now, let's create an object:
{ type: ADD_NEW_DOCTOR_REQUEST, data: { name: ‘Dr. Yoshmi Mukhiya’, age: 22, department: ‘Mental Health’, telecom: ‘99999999’ } }
This is a simple, plain JavaScript object, and it is referred to as an action. An action must have the type
property that defines the type of action to be performed. In this use case, the action is adding an action. The type is basically a string constant. In any web application, there are a multitude of actions required. So, the general (and most common) trend is to separate these actions into separate files and import them into the required place.
Now, let's assume that we need to delete a doctor's record from our app. We should be able to create an action object easily, as follows:
{ type: 'DELETE_DOCTOR_REQUEST', identifier: 201, }
Now, go ahead and create the actions for the following:
- Adding a user to the hospital management system
- Deleting a user from the hospital management system
- Updating a user
JavaScript functions that take some arguments and return actions are action creators. Let's look at an action creator function for adding a new doctor to the application:
function addNewDoctor(data) { return { type: ADD_NEW_DOCTOR_REQUEST, data }; }
Now, you can think of a function that you might need for deleting a record, as follows:
function deleteDoctor(identifier) { return { type: "DELETE_DOCTOR_REQUEST", identifier }; }
Before we move on to reducers, let's make one more action creator for authentication. Generally, to authenticate, we use an email and password. So, in order to authenticate (or deauthenticate) we need to define actions. Please note that the actions that we define will be used in our project for a hospital management system. Our action for authentication could look something like the following:
export const authenticate = (credentials) => ({ type: "AUTHENTICATE", payload: credentials }); export const deauthenticate = () => ({ type: "DEAUTHENTICATE" });
Similarly, let's create action creators for registering a user. When we register a user, we are likely to have a request, a success, or a failure. Based on these three states, we can create the action creators, as follows:
export const onRegisterRequest = user => ({ type: REGISTER_REQUEST, user }); export const onRegisterSuccess = user => ({ type: REGISTER_SUCCESS, user }); export const onRegisterFailure = message => ({ type: REGISTER_FAILURE, message, });
JavaScript functions that take actions and states as input and return the new states are reducers. Well, if this is confusing, try to keep in mind that the action only describes what happened, not how the application state transforms.
It is very important to understand the reducer function. Let's consider our hospital management system. Our application's state can look like the following:
{ doctors: [ { name: "John Doe", department: "Radiology", address: "Kathmandu, 4017, Nepal", telecom: "999-999-999" }, { name: "Ola Nordmann", department: "General Physician", address: "Kong Oscarsgate 29, 5017, Bergen, Norway", telecom: "111-111-1111" } ]; }
When creating a reducer function, it is important that we remember the reducer principle: it must be a pure function. It should just take the action and return a new state, with no side effects, no mutations, and no API calls.
Let's consider another example of a content management system. In a normal CMS, we have posts and categories. So, our state at an instance could look like the following:
{ posts: [ { user: 'John Doe', category: 'Practitioner', text: 'This is the first post about Practitioner.' }, { user: 'Ola Nordmann', category: 'Patients', text: 'This is the first post about Patients.' } ], filter: ‘Patients’ }
There's nothing complicated here, right? Now, let's start to write our reducer function for both use cases: our CMS use case and our hospital management system use case.
We will start by defining an initial state. Let's initiate our initial state by creating an empty object with an array of empty doctors:
const initialState = { doctors: [] };
In any database, there is a need for creating, updating, reading, and deleting resources. Similarly, in the hospital management system, we need to read a doctor's record, create a new record, update it, or delete it. Hence, we are likely to have multiple action objects defined, as we mentioned in the preceding section.
This introduces a requirement to handle reducer functions for each of the actions. We can create a single reducer function to handle a similar scenario, and make use of the switch
case to handle multiple action types:
import { ADD_NEW_DOCTOR_REQUEST, } from './actions' function addDoctor(state = initialState, action) { switch (action.type) { case ADD_NEW_DOCTOR_REQUEST: return Object.assign({}, state, { doctors: [ ...state.doctors, { name: action.name, age: action.age, department: action.department, telecom: action.telecom } ] }); default: return state; } }
In the preceding snippet, we have defined ADD_NEW_DOCTOR_REQUEST
in the actions. We can check the action type for deleting the doctor's record. Go ahead and add a reducer use case for deleting a doctor.
Now, your task is to check the initial state of the CMS system and write reducer functions for CREATE_POST
, EDIT_POST
, and SET_FILTER
. Once you have finished writing the reducer function, it should look something like the following:
import { CREATE_POST, EDIT_POST, SET_FILTER } from './actionTypes' function postsReducer (state = [], action) { switch (action.type) { case CREATE_POST: { const { type, ...post } = action return [ ...state, post ] } case EDIT_POST: { const { type, id, ...newPost } = action return state.map((oldPost, index) => action.id === index ? { ...oldPost, ...newPost } : oldPost ) } default: return state } }
The store stores all of the states of the application. Hence, it is sometimes referred to as the heart of the application. The most important point to note is that there is a single store in the entire application. To create a store, we can use the createStore
function provided by Redux:
import { createStore } from 'redux' import doctorsReducer from './reducers' const store = createStore(doctorsReducer)
The methods for stores will be explained in the following subsections.
The getState()
method gives you the current state of any application, which is equal to the last value returned by the application's reducer.
As the name suggests, dispatch(action)
only dispatches the action. The main point to keep in mind is that this is the single way to modify the state.
It is quite important to understand the Redux life cycle. To understand the Redux life cycle, you must understand the steps involved in a complete cycle. A user interacts with an interface through some events, like clicking on a button to create some resource. For example, to save a doctor record to a database, the user enters the relevant information and hits the Save
button. These events initiate some actions. As we mentioned previously, an action is a pure JavaScript object that tells us what happened.
Redux confirms whether the dispatched action contains the type
property. After the confirmation, it is passed the main reducer.This is referred to as dispatching an action. An action is dispatched using the following function:
store.dispatch(action)
The entire concept of how Redux operates is illustrated in the following diagram:
![](https://static.packt-cdn.com/products/9781789610086/graphics/4dc1c08b-60a5-478c-ab03-84699e4285fb.png)
The main reducer function, when called with the current state and dispatched action, passes the sub-states and action down to another reducer. As we mentioned in the previous section, the reducer is just a function, and it uses the previous state and provides the new state. Developers prefer to split the state tree into multiple slices and create a separate reducer for each state slice. Actions, on the other hand, can be concerned with more than one state slice. This method of splitting the reducers into smaller and easier to understand pieces is termed decomposition.
The new state is returned by the main reducer function and is saved in the Redux store, and all listener functions that are subscribed via store.subscribe()
get called. This causes the re-render of user interfaces. We will look at the concept of Redux middleware more in Chapter 6, Extending Redux by Middleware.
So far, we have gone over a lot of theoretical concepts. If it does not make sense entirely, do not worry. It takes some time to sync with the concepts and the flow. To get better insight into how this works, let's get started with the very basic concept of making your first Redux Hello World application.
Let's get started with using Redux. We will start with basic configurations, and we can take the configurations further in each chapter:
- Installing
node
andnpm
/yarn
is done as follows:
Install the latest version of a node
from https://nodejs.org/. To verify the correct installation, run the following command:
node --version
You can get the latest version of yarn
from https://yarnpkg.com/en/, and npm
from https://www.npmjs.com/get-npm.
- Initialize the project as follows:
The first thing is to initialize the project. In this project, we are going to use yarn
. The easiest way to initialize the project with package.json
is to run the init
command:
yarn init OR npm init
Follow the onscreen instructions and provide the details. It will ask about the name of the project, the description, the author name, the version, the license, and the entry point. Most of the information can be customized according to your requirements.
Now, create a source folder, src
, to include all of our code. The next step is to set up webpack
. The minimal package.json
file looks like the following:
{ "name": "gettting-started-with-redux-ch01", "version": "1.0.0", "description": "Getting Started With Redux", "main": "src/app/app.js", "scripts": { "start": "webpack-dev-server", "build": "webpack" }, "author": "Suresh KUMAR Mukhiya", "license": "MIT", "devDependencies": { "@babel/core": "7.2.0", "@babel/preset-env": "7.2.0", "@babel/preset-react": "7.0.0", "babel-core": "6.26.3", "babel-loader": "8.0.4", "babel-plugin-transform-object-rest-spread": "6.26.0", "webpack": "4.27.1", "webpack-cli": "3.1.2", "webpack-dev-server": "3.1.10" }, "dependencies": { "redux": "4.0.1" } }
- Configuring
webpack
is done as follows:
You can read more about webpack
on their official documentation site (https://webpack.js.org/). The first thing is to install webpack
and webpack-dev-server
:
yarn add webpack webpack-dev-server --dev
Now, we need to configure the webpack
. We can do that in different ways. A lot of information about webpack
configuration can be found on the documentation site (https://webpack.js.org/configuration/). The minimum configuration that we need is as follows:
const path =require('path') module.exports ={ Mode: ‘development’, entry:'./app/app.js', output: { path: path.resolve('dist'), filename: 'main.js' },
Let's place the webpack
configuration files into the webpack
folder and create a base configuration file, called webpack.config.js
. The loaders in the webpack
tell the webpack
what to do with the entry file(s). We are going to use Babel to transpile our JavaScript files; so, let's define the babel-loader
for .js
files, as follows:
const path = require("path"); module.exports = { mode: "development", entry: "./src/app/app.js", output: { path: path.resolve("dist"), filename: "main.js" }, module: { rules: [ { test: /\.jsx?$/, use: { loader: "babel-loader" }, exclude: /node_modules/ } ] } };
Generally, what we have is the bare minimum code required for Webpack configuration. However, in a real application, we would like to compile more resources than just the JavaScript files, including JS files, CSS files, fonts, image files, and others. We can configure these with webpack.
The most standard practice is to split the configuration into two types: development configuration and production configuration. By now, you might have already realized the need for separate configurations. A detailed blog article about this need and its process can be found at https://www.hacksoft.io/blog/split-your-webpack-configuration-development-and-production/. To keep the configuration simple and elegant, we have created three files for webpack; namely, webpack.base.babel.js
, webpack.dev.babel.js
, and webpack.prod.babel.js
. You can find a similar configuration in the starter file in the GitHub repository.
- Babel is configured as follows:
We will use Babel to compile JavaScript files. Let's configure it by installing babel
and its related libraries:
yarn add @babel/core @babel/preset-env @babel/preset-react babel-core babel --dev --exact yarn add babel-loader babel-plugin-transform-object-rest-spread --dev --exact
More configuration related to Babel can be found at https://babeljs.io/docs/usage/api/#options.
For the minimum configuration, we will go with creating a babel.config.js
file and adding an entry, as follows:
module.exports = { presets: [ [ "@babel/preset-env", { modules: false } ], "@babel/preset-react" ], plugins: ["transform-object-rest-spread"] };
This file just indicates which libraries we are using in order to compile our JavaScript files. For example, we are going to use a transform-object-rest-spread
library to utilize the spread feature. Learn more about this library at https://babeljs.io/docs/en/babel-plugin-proposal-object-rest-spread. We can add other polyfill plugins that we plan to use throughout the project later on. Babel can be configured in multiple ways. We can also create a .babelrc
file to configure it. You will find a working example in Chapter 2, Testing.
- Define the entry file:
The entry file indicates the startup file. We will point our entry file to app/app.js
. This file contains the main entry codes. This file will be transpiled into main.js
by Babel. Secondly, we will create an index.html
file, which acts as the entry file for our application:
<!doctype html> <html lang="en"> <head> <!-- The first thing in any HTML file should be the charset --> <meta charset="utf-8"> <!-- Make the page mobile compatible --> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Allow installing the app to the homescreen --> <meta name="mobile-web-app-capable" content="yes"> <link rel="icon" href="/favicon.ico" /> <title>Redux-Book-starter</title> </head> <body> <div id="root"></div> </body> <script src="dist/main.js"></script> </html>
Also, inside of the app/app.js
, we can try to log some text to verify that the configuration is working fine:
console.log("Welcome to Redux Programming");
Now, the last step is to configure the webpack
script to run the build. This can be done by placing scripts in the package.json
file:
"scripts": { "start": "webpack-dev-server", "build": "webpack" },
The first step is to build the files. To do that, simply run yarn build
from your command line. Now, we can run the webpack
by using the yarn start
command from the command line. Note that this is the minimum configuration required to get started with Redux. Our aim here is for you to learn Redux. So, we are going to use starter files for each of the projects, which can be found in the GitHub repository for this book. The starter files, if preconfigured with Babel, Webpack, and Eslint, are ready to be consumed for further development.
- Installing Redux:
To get started with Redux, we need to add Redux to our dependencies list, as follows:
yarn add redux --exact
Now, from the project root folder, inside of your command line, run yarn start
and open http://localhost:8080
in your favorite browser, checking the console. You should see the log we have in yourapp/app.js
.
Let's implement a simple example to turn on and turn off the light. We can build a simple robot that just listens to commands and, based on the commands, performs some actions. For simplicity, suppose that our robot can only understand two commands, as follows:
TURN_ON
TURN_OFF
Now, let's build our robotic function:
const tubeLight = (state = "OFF", action) => { switch (action.type) { case "TURN_ON": return "ON"; case "TURN_OFF": return "OFF"; default: return state; } };
This is a simple JavaScript function that takes the initial state and action as parameters and returns a state. That sounds like something familiar, doesn’t it? Yup; you are right. This is a simple reducer function.
In the first section, you learned Redux's first principle: a single source of truth. Redux provides a function called createStore
that takes the main reducer file and creates the store. Let's create a store, as follows:
import { createStore } from "redux"; const store = createStore(tubeLight);
So far, so good. So, what did we do here? We imported the createStore
function from the Redux library that is given tubeLight
, which is a reducer, as an argument and is saved into a variable called a store. Here, you can recall a functional programming concept. A function can consume another function. Now, as you have already seen, the store has three methods: getState
, dispatch
, and subscribe
. Let's use them.
log
the initial state, as follows:
console.log("Initially tubelight is: ", store.getState());
Try to build it, and run it again (yarn build && yarn start
). Check the console:
Initially tubelight is: OFF
Nothing complex, right? We provided the initial state to OFF
, and it logged the initial state as OFF
. That looks good. Now, let's try to modify the store. In other words, we should instruct the robot to turn on the tubelight
. Remember, we can only modify the store by using a dispatch
function. Now, we can use that function and log the state, in order to see the state change:
store.dispatch({ type: "TURN_ON" }); console.log("Now tubelight is: ", store.getState());
The output that you get on the console should be as follows:
Now tubelight is: ON
Now, it makes sense, right? Let's go further and display the state on the browser, rather than on the console. To do that, let's create a button. When we press the button, it should toggle the tubelight
state. That is to say, if the tubelight
is ON
, we turn it off, and vice versa. To make it simple, let's forget about React and use native JavaScript:
const button = document.createElement("button"); button.setAttribute("id", "lightButton"); var text = document.createTextNode("Toggle Light"); button.appendChild(text); document.body.appendChild(button);
The preceding snippet will create a simple button on the browser, with the text Toggle Light
and the ID lightButton
.
Now, we need to add an event listener. That is to say, if the tubelight is on, we turn it off by clicking on the button. We can do that as follows:
document.getElementById("lightButton").addEventListener("click", () => { if (store.getState() === "ON") { store.dispatch({ type: "TURN_OFF" }); } else { store.dispatch({ type: "TURN_ON" }); } });
Now, let's render that in the browser, inside of the body
tag:
const render = () => { document.body.innerText = store.getState(); document.body.appendChild(button); };
This will render the initial state of the store. But we need to display when the state changes. To do that, our third method of the store comes into play (subscribe()
):
store.subscribe(render); render();
Now, try to build the app and run it (yarn build && yarn start
). Try to click on the button to change the state, and see whether the state is reflected on the browser. Pretty sweet, right? You can find the working example of this code in the GitHub repository, inside CH01/getting-started
.
Manually updating the DOM does not scale in a real application. To do so, we use the help of other libraries, such as React. We will configure React with Redux and use it to understand other complex scenarios in Redux.
Having understood the concept of Redux, let's get started with the project that we promised to work with. We will continue to use the project from the getting-started section to build the other components required for the project. Throughout this entire book, we will develop a multilingual hospital management system. Of course, the development of a complete hospital management system is out of the scope of this book, but we are going to get started with a simple one. We are going to have an authentication system and a CRUD (Create, Read, Update, and Delete) of users to get started with.
Since we know what a store is in Redux, let's get started with creating a store file. We can keep our store in a separate file. Let's call it configureStore.js
:
import { createStore, applyMiddleware, compose } from "redux"; import createReducer from "./reducers"; export default function configureStore(initialState = {}, history) { const store = createStore( createReducer(), ); // Extensions store.injectedReducers = {}; // Reducer registry return store; }
Our root reducers can reside in a reducers.js
file. We are going to use the combinedReducers
utility function from the Redux library:
import { combineReducers } from "redux"; import history from "utils/history"; export default function createReducer(injectedReducers = {}) { const rootReducer = combineReducers({ ...injectedReducers }); return rootReducer; }
This is the main file, app.js
, which will be the main entry file for our project. We are going to put the file inside of app/app.js
. You can see that we are using some of the npm
packages, including @babel/polyfill
, react
, react-dom
, react-redux
, and sanitize.css
:
// Needed for redux-saga es6 generator support import "@babel/polyfill"; // Import all the third party stuff import React from "react"; import ReactDOM from "react-dom"; import { Provider } from "react-redux"; import history from "utils/history"; import "sanitize.css/sanitize.css"; // Import root app import App from "containers/App"; import configureStore from "./configureStore"; // Create redux store with history const initialState = {}; const store = configureStore(initialState, history); const MOUNT_NODE = document.getElementById("app"); const render = () => { ReactDOM.render( <Provider store={store}> <App /> </Provider>, MOUNT_NODE ); }; render();
Let's briefly go over these packages, as follows:
@babel/polyfill
(https://babeljs.io/docs/en/babel-polyfill): Babel polyfill has a polyfill that contains a custom regenerator runtime andcore-js
. In other words, it allows us to consume the full set of ES6 features, beyond syntax changes, including built-in objects likePromises
andWeakMap
, as well as new static methods, likeArray.from
orObject.assign
.react
: We already know what React is. We are going to dive deeper into creating React components in Chapter 6, Extending Redux by Middleware.react-dom
: React DOM helps us to glue React and the DOM. When we want to show our React components on the DOM, we need to utilize thisReactDOM.render()
function from React DOM. We will discuss these features more in the upcoming chapters.React-redux
: This allows us to communicate, in both ways, between React and Redux (https://github.com/reactjs/react-redux). It is a binding between React and Redux that allows us to create containers and listen to the store changes, reflecting that into a presentational component. We will explore container components (smart components) and presentational components (dumb components) in more detail in upcoming chapters.Sanitize.css
(https://github.com/csstools/sanitize.css): This is one of the cascading style sheet libraries that yield consistent, cross-browser default styling of HTML elements, as well as useful defaults.
We used a history
object in app.js
. We can create history.js
inside of the utils
folder and create an instance of history
. You can learn more about history
from https://github.com/ReactTraining/history. In a nutshell, the history
library manages the session history
everywhere that JavaScript runs:
import createHistory from "history/createBrowserHistory"; const history = createHistory(); export default history;
Let's create our first container component, inside of app/containers/App/index.js
:
import React from 'react'; import HomePage from 'containers/HomePage/Loadable'; export default function App() { return ( <div> <HomePage /> </div> ); }
The home page container contains two files, Loadable.js
and index.js
:
import loadable from 'loadable-components'; export default loadable(() => import('./index'));
The index.js
is as follows:
import React, { PureComponent } from 'react'; /* eslint-disable react/prefer-stateless-function */ export default class HomePage extends PureComponent { render() { return <h1>This is the HomePage Redux-book container!</h1>; } }
The complete code for this project can be found in the GitHub repository, inside of the CH01
starter files. We are going to continue using it in other chapters. Once you have these files up in your editor, we can start to run our first application. To run the application, the first thing to do is install the npm
dependencies, as follows:
yarn install yarn run
The application should start at http://localhost:8080/
.
Redux is one of the most popular libraries used for state management in the frontend ecosystem today. In this chapter, we discussed the need for Redux and the principles that make the library stand out. Moreover, we discussed some of the fundamental concepts of functional programming, and how these concepts are used in the development of the Redux library. We also covered some of the Redux ecosystem and the Redux life cycle, and we created an outline for the project. We implemented the bare minimum version of Redux, extending it to create a larger project.
In the next chapter, you will learn about test-driven development, and we will set up the JEST framework for testing. Moreover, we will continue to use the code that we developed in this chapter in the upcoming chapters.
We outlined the basic architecture of Redux and its ecosystem in this chapter. However, we are aware of the fact that it is not easy to understand everything in one go. The following is a list of resources that you can consider to get further knowledge:
- https://redux.js.org/.
- Learning Redux, by Daniel Bugl, August 2017, Packt Publications.
- https://babeljs.io/docs/en/.
- React: Tools and Resources, by Michael Wanyoike; Manjunath, M.; Jack Franklin; Swizec Teller; and Ahmed Bouchefra.
- Learning React: Functional Web Development with React and Redux, by Alex Banks and Eve Porcello.
- Abelson, Harold; Sussman, Gerald Jay (1984). Structure and Interpretation of Computer Programs. MIT Press. Section 1.3, Formulating Abstractions with Higher-Order Procedures. ISBN 0-262-01077-1.
- Mukhiya, S. K. and Hoang Hung, K. (2018). An Architectural Style for Single Page Scalable Modern Web Applications, 5(4), 6–13. Retrieved from https://www.ijrra.net/Vol5issue4/IJRRA-05-04-02.pdf.