Redux — Powerful State Management Tool

Formal Definitions:
Redux is a powerful tool for managing state in JavaScript applications, offering predictability and clarity in how your application’s data changes over time. Let’s dissect its definition and core components to understand its essence.
JavaScript Application Compatibility:
Redux isn’t tied to any specific JavaScript framework like React; it’s versatile and can be integrated seamlessly with various frameworks such as React, Angular, Vue, or even used independently with plain vanilla JavaScript.
Predictable State Management:
Redux provides a predictable state container. This means that all state transitions within Redux are manageable and traceable, ensuring clarity and control over how your application’s state evolves.
State Container:
At its core, Redux acts as a state container, holding the entire state of your application. This includes dynamic data such as user information, product details, or any other relevant information needed for your application to function.
Three Key Concepts of Redux:
- Store: Analogous to a shop in a physical store scenario, the Redux store holds all the application’s state. It serves as a central repository for your data.
- Reducer: Similar to a shopkeeper, a reducer function in Redux manages state transitions based on actions. It listens to actions dispatched by your application and modifies the state accordingly.
- Actions: Actions represent the intention to change the state. Just like a customer instructs the shopkeeper to perform a specific task, actions in Redux dictate what changes need to be made to the state.
Three Principles of Redux:
- Single Immutable State Tree: Redux maintains the application state in a single, immutable object. This centralized approach simplifies state management and ensures consistency across your application.
- Actions as the Sole Source of Change: In Redux, the only way to modify the state is by dispatching actions. These actions describe what needs to be done and serve as a blueprint for state modifications.
- Pure Reducers for State Transformation: Reducers in Redux are pure functions responsible for transforming the state based on dispatched actions. They take the current state and an action as input and return a new state, without modifying the original state.
Basic Flow of a Redux Application:
- Initialize the Redux store, which holds the application’s state.
- Utilize
getState()
to access the current state of the application. - Subscribe to the store to receive updates whenever the state changes.
- Dispatch actions to the store to trigger state transitions based on predefined logic.

Let’s create basic application using redux which will help understand the topic better:
const initialState = {
numOfCakes:10,
numOfIcecreams:20
}
Here we are using vanilla javascript to create basic application that will contain an initial state with the above mentioned data. Now we will perform state transitions as performed in a application using central store.
The only way to change the following state is to emit an action, an object describing what happened.
const BUY_CAKE = 'BUY_CAKE';
const BUY_ICECREAM = 'BUY_ICECREAM';
Above are two actions to buy cake and ice cream, we need an action object with type property to dispatch to the reducer. An action object is necessary as actions often contain additional data to be transported to the reducer. Every action object will have a type property representing an action to be performed.
const buyCakeActionObj = {
type:BUY_CAKE,
info:'action to buy cake'
}
const buyIcecreamActionObj = {
type:BUY_ICECREAM,
info:'action to buy icecream'
}
We can use these action objects directly into our application but a better way up is to create action creators, these are functions that return the action object.
function buyCake(){
return buyCakeActionObj;
}
function buyIcecream(){
return buyIcecreamActionObj;
}
Now, let’s finally create the reducer for our application. To specify how the state tree is transformed by actions, we write pure reducers. Pure reducers are basically pure functions which take the state and action to be performed as parameters and return the updated state.
const reducer = (state = initialState, action) => {
switch(action.type){
case BUY_CAKE: return {
...state,
numOfCakes: state.numOfCakes - 1
}
case BUY_ICECREAM: return {
...state,
numOfIcecreams: state.numOfIcecreams - 1
}
default: return state
}
}
It’s time to create the redux store. It holds the entire application state.
const redux = require('redux');
const createStore = redux.createStore; // redux store
const store = createStore(reducer); // passing reducer to out store
Let’s use the getState() method to access the state of our application. It will return the current state of the application.
console.log("Initial State: " , store.getState());
Now, we need to subscribe to the store to get updated whenever the state changes. The function subscribe returns a method that will be used to unsubscribe to store later in the application or whenever needed.
const unsubscribe = store.subscribe(() => console.log('Updated state: ' ,store.getState()));
At the final step, to perform state transitions, we need to dispatch actions to the store.
store.dispatch(buyCake()); // dispatching the action to reducers
store.dispatch(buyIcecream());
unsubscribe(); // unsubscribed to the store
You can now test the application buy calling the buyCake() and buyIcecream() methods and see the the state changing and the updated state will be logged on the console.

Asynchronous Actions
Async actions are utilized to perform asynchronous API calls, retrieving data from an endpoint and integrating it into our application. They facilitate fetching information from an external API in a non-blocking manner, ensuring seamless integration with the application’s logic and flow.
Now we will develop an application that fetches users from an API endpoint and stores the data in Redux store.
Create an index.js file, or with any name of your choice and let’s start coding our application step by step.
const initalState = {
loading:true,
users:[],
error:''
}
Create the initial state as above. Here we have a loading flag which notifies the data is currently being fetched, users which contain array of users fetched and error to contain any error message, in case any errors are encountered.
Now we will be define our actions, we will have three type of actions in our application.
FETCH_USERS_REQUEST — Fetch list of users
FETCH_USERS_SUCCESS — Fetched Successfully
FETCH_USERS_FAILURE — Error while fetching the users
// actions
const FETCH_USERS_REQUEST = 'FETCH_USERS_REQUEST'; // to make request to API.
const FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS'; // success response from API.
const FETCH_USERS_FAILURE = 'FETCH_USERS_FAILURE'; // failure response from API.
// action creators
const fetchUsersRequest = () => {
return {
type:FETCH_USERS_REQUEST
}
}
const fetchUsersSuccess = (users) => {
return {
type:FETCH_USERS_SUCCESS,
payload:users
}
}
const fetchUsersFailure = (error) => {
return {
type:FETCH_USERS_FAILURE,
payload:error
}
}
Defining reducer for our application.
// reducer
const reducer = (state = initalState,action) => {
switch(action.type){
case FETCH_USERS_REQUEST:
return {
...state,
loading:true
}
case FETCH_USERS_SUCCESS:
return {
loading:false,
users:action.payload,
error:''
}
case FETCH_USERS_FAILURE:
return {
loading:false,
users:[],
error:action.payload
}
}
}
Redux Thunk
Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.
const fetchUsers = () => {
return async (dispatch) => {
dispatch(fetchUsersRequest());
try {
const res = await axios.get('https://jsonplaceholder.typicode.com/users');
console.log("Data Fetched!!!");
const users = res.data;
console.log("Users:",users);
dispatch(fetchUsersSuccess(users));
} catch (error) {
dispatch(fetchUsersFailure(error.message));
}
}
}
Now, we need to create a redux store.
const redux = require('redux');
const createStore = redux.createStore;
const applyMiddleware = redux.applyMiddleware;
const thunk = require('redux-thunk').default;
//! store
const store = createStore(reducer,applyMiddleware(thunk));
const unsubscribe = store.subscribe(() => console.log(store.getState()));
store.dispatch(fetchUsers());
unsubscribe();
Now, if we run the following piece of code, the fetchUsers() action is dispactched which fetches the users from api endpoint through axios.

We receive the console logs like this!
In conclusion, Redux offers a powerful state management solution for complex JavaScript applications, providing a predictable and centralized data flow that enhances maintainability, scalability, and developer productivity.