Editorial Note: This Redux tutorial aims to help you with the how and why of Redux. Please note that you can write Redux apps with vanilla JavaScript or with JavaScript frameworks and libraries like Angular, React, Ember or jQuery.
The trend towards Single Page Applications (SPAs) has been increasing across responsive websites. On the whole, a SPA is just a web application that uses one HTML web page as an application shell and whose end-user interactions are implemented with JavaScript, HTML, and CSS.
This article is published from the DNC Magazine for Developers and Architects. Download this magazine from here [PDF] or Subscribe to this magazine for FREE and download all previous and current editions.
These days, it’s common to build SPAs using frameworks/libraries such as Angular or React.
With great power comes great complexity and while SPAs can help you build fast and fluid User Interfaces (UI), they can also introduce new problems that we haven’t dealt in with old style web applications.
Handling the data flow in SPA can be very hard and managing application states can be even harder. If you don’t handle data flow and states correctly, you can expect your application behavior to be unpredictable, inconsistent and untestable.
How do you tackle data flow and application state complexity?
There are many ways. For example, you can apply the Command Query Responsibility Segregation (CQRS) pattern, which isolates queries that read data from commands updating that data. Another option is the Event Sourcing pattern, which ensures that all changes in state are stored as a sequence of events.
In this article, we will explore the Redux pattern and how it can help tackle these SPA complexities.
Flux and Redux Patterns
In order to understand the Redux pattern, we should start with the Flux pattern. The Flux pattern was introduced by Facebook a few years ago.
Flux is a unidirectional data flow pattern that fits into the components architecture and blends with frameworks and libraries such as React and Angular 2.
Flux includes 4 main players: actions, dispatcher, stores and views. Figure 1 describes this pattern:
Figure 1: Flux Pattern
When a user interacts with a view, the view propagates an action to a central dispatcher. The dispatcher is responsible to propagate actions to one or many store objects.
Store objects hold the application data and business logic and are responsible to register to actions in the dispatcher. They also update the view which is affected by a specific action that indicates data/state has changed. The responsibility of the view is to update according to the new data/state, and to interact with users.
Another option to enter the data flow is an outside action which is not related to a view, such as timer callbacks or Ajax callbacks.
Now that we understand how the Flux pattern works, let’s drill down into Redux principles before we explore the Redux data flow and how it relates to Flux.
Redux Pattern Principles
Redux is based upon 3 major principles:
1. Single source of truth
2. State is read-only
3. Changes are made with pure functions
Single source of truth
In Redux, there is only one store object. That store is responsible to hold all the application state in one object tree.
Having only one store object helps to simplify the debugging and profiling of the application because all the data is stored in one place. Also, difficult functionality such as redo/undo becomes simpler because the state is located in one place only.
The following example demonstrates how you get the state from a store object using the getState function:
let state = store.getState();
State is read-only
The only way to mutate the state that is held by the store, is to emit an action that describes what happened. State can’t be manipulated by any object and that guards us from coupling problems and from some other side effects. Actions are just plain objects that describe the type of an action, and the data to change.
For example, the following code block shows how to dispatch two actions:
store.dispatch({
type: 'ADD_GROCERY_ITEM',
item: { productName: 'Milk' }
});
store.dispatch({
type: 'REMOVE_GROCERY_ITEM',
index: 3
});
In the example, we dispatch an add grocery item action, and a remove grocery item action. The data in the action objects is the item to add or the index of the object to remove.
Changes are made with pure functions
In order to express how state transition occurs, you will use a reducer function. All reducer functions are pure functions. A pure function is a function that receives input and produces output without changing the inputs.
In Redux, a reducer will receive the previous state and an action, and will produce a new state without changing the previous state. For example, in the next code block you can see a groceryItemsReducer which is responsible to react to add grocery item action:
function groceryItemsReducer(state, action) {
switch (action.type) {
case 'ADD_GROCERY_ITEM':
return object.assign({}, state, {
groceryItems: [
action.item,
…state.groceryItems
]
};
default:
return state;
}
}
As you can see, if an action isn’t recognized by the reducer, you return the previous state. In case you got an add grocery item action, you return a copy of the previous state that includes the new item.
Now that we understand the main Redux principles, we can move on to the Redux data flow.
Redux Data Flow and how is it different from Flux
The Redux data flow is based on Flux but it’s different in a lot of ways.
In Redux there is no central dispatcher. Redux includes only one store object, while Flux allows the usage of multiple stores.
In order to mutate the stored Redux state, you use reducer functions instead of inner business logic functionality that exists in Flux stores.
All in all, Redux is a new pattern that took some aspects of Flux and implemented them differently.
The following figure show the Redux data flow:
Figure 2: Redux Data Flow
As you can see, the main players really resemble the players in Flux, except for the reducers and the lack of dispatcher object.
In the Redux data flow, a user interaction or an asynchronous callback will produce an action object. The action object will be created by a relevant action creator and be dispatched to the store. When the store receives an action, it will use a reducer to produce a new state. Then, the new state will be delivered to views and they will update accordingly. This data flow is much simpler then Flux and helps to produce a predictable state to the application.
The Redux Library
The Redux library was created by Dan Abramov. On the whole, it is a small and compact library that includes a small set of API functions. In order to get started with the library, you can install it using npm:
npm install --save redux
or download it from its repository on GitHub: https://github.com/reactjs/redux.
The Redux library includes the following main API functions:
- createStore(reducer) – function to create a new store with the given reducer function.
- combineReducers(reducers) – function that helps you to combine the given array of reducers into one reducer. It can be helpful to divide your reducers into different aspect and then to use this function to produce the application main reducer.
- applyMiddleware(middlewares) – function that add middlewares to the data flow pipeline. Middlewares are functions that wrap the store dispatch function and enables you to add functionality that runs before a dispatch occurs. Middlewares can be composed together to create interesting functionality such as logging or even dispatching of asynchronous operations. In the article, I won’t cover middlewares usage but I encourage you to seek information about this concept.
- store API – Once a store is created, it includes a few API functions such as dispatch and getState.
Now that you know about the Redux library, let’s see it in action.
Building a Simple App with Redux
Note: This example assumes that you have node and npm installed on your machine. If you don’t have node and npm installed on your machine, go to the following link to download and install them: https://nodejs.org/en/download/.
It is also assumed that you have some TypeScript knowledge.
Create a new empty project and give it the name ReduxInAction.
Run npm init and initialize a new package.json file. In the package.json, add the following main and scripts properties:
"main": "src/index.js",
"scripts": {
"test": "node src/index.js"
}
Once you finished editing the package.json file, run the following command in the command line:
npm install –save redux
This command will install the Redux library in your project. Add a new tsconfig.json file and add to it the following code:
{
"compilerOptions": {
"moduleResolution": "node",
"module": "commonjs",
"target": "es5",
"sourceMap": true
},
"exclude": [
"node_modules"
]
}
Add a new src folder under the root of the project. In the src folder, add two new folders: actions and reducers.
In the actions folder, create an index.ts file and add to it the following code:
import {Action} from "redux";
export default class CounterActions {
static INCREMENT: string = 'INCREMENT';
static DECREMENT: string = 'DECREMENT';
increment(): Action {
return {
type: CounterActions.INCREMENT
};
}
decrement(): Action {
return {
type: CounterActions.DECREMENT
};
}
}
The CounterActions class is an action creator class which also contains the action types that are included in our application.
In the reducers folder, create an index.ts file and add to it the following code:
import CounterActions from "../actions/index";
const INITIAL_STATE = 0;
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case CounterActions.INCREMENT:
return state + 1;
case CounterActions.DECREMENT:
return state - 1;
default:
return state;
}
}
The reducer has the implementation of how to mutate the current state and produce a new state. Once an increment action arrives, the state is incremented by 1. Once a decrement action arrives, the state is decremented by 1.
Now you can create the application shell and run it. In the src folder, add index.ts file and add the following code to it:
import {createStore} from 'redux';
import counterReducer from './reducers';
import CounterActions from "./actions/index";
const store = createStore(counterReducer);
const counterActions = new CounterActions();
console.log(store.getState()); // output 0 to the console
store.dispatch(counterActions.increment() as any);
console.log(store.getState()); // output 1 to the console
store.dispatch(counterActions.increment() as any);
console.log(store.getState()); // output 2 to the console
store.dispatch(counterActions.decrement() as any);
console.log(store.getState()); // output 1 to the
At first create a store using the createStore function and then give the store the counterReducer created earlier. Then create a new CounterActions instance. Then print to the console a set of operations to perform on the store.
Once the application is in place, run the TypeScript compiler to compile all the files in the project:
tsc -p
Then run the command npm run test to test the output that is written in the console.
This is a simple example of how to use Redux and it doesn’t include any UI. In a follow up article, I’ll cover how to combine Redux with React library.
Summary
Redux became a very popular data flow pattern and is being used in many applications.
The Redux library is now a part of the efforts made by Facebook to build open source libraries. During the last year, I had the opportunity to use the pattern in several projects and it really proved itself by helping to simplify very complicated features.
While Redux can help to simplify data flow in big SPAs, it isn’t suitable to every application and in simple or small applications, it can produce a lot of unnecessary coding overhead.
I encourage you to deep dive into Redux and there is a free course that was recorded by Redux library creator Dan Abramov which delves into features that weren’t covered in this article: https://egghead.io/courses/getting-started-with-redux.
Download the entire source code of this article (Github)
i
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Gil Fink is a web development expert, ASP.Net/IIS Micrsoft MVP and the founder of sparXys. He conducts lectures and workshops for individuals and enterprises who want to specialize in infrastructure and web development. He is also co-author of several Microsoft Official Courses (MOCs) and training kits, co-author of “Pro Single Page Application Development” book (Apress) and the founder of Front-End.IL Meetup. You can get more information about Gil in his website
gilfink.net