Redux is a predictable state container for JavaScript apps.

Redux is state management system for cross-component or app-wide state.

As the application grows, it becomes difficult to keep it organized and maintain data flow.

Redux solves this problem by managing application’s state with a single global object called Store.

Redux fundamental principles help in maintaining consistency throughout your application, which makes debugging and testing easier.

With Redux, the state of your application is kept in a store, and each component can access any state that it needs from this store.

More importantly, it gives you live code editing combined with a time-travelling debugger. It is flexible to go with any view layer such as React, Angular, Vue, etc.

Principles of Redux

Predictability of Redux is determined by three most important principles as given below −

Single Source of Truth

The state of your whole application is stored in an object tree within a single store. As whole application state is stored in a single tree, it makes debugging easy, and development faster.

State is Read-only

The only way to change the state is to emit an action, an object describing what happened. This means nobody can directly change the state of your application.

Changes are made with pure functions

To specify how the state tree is transformed by actions, you write pure reducers. A reducer is a central place where state modification takes place. Reducer is a function which takes state and action as arguments, and returns a newly updated state.

Redux – Installation

Before installing Redux, we have to install Nodejs and NPM.

To install redux, you can follow the below steps −

Run the following command in your command prompt to install Redux.

npm install --save redux

To use Redux with react application, you need to install an additional dependency as follows −

npm install --save react-redux

To install developer tools for Redux, you need to install the following as dependency −

Run the below command in your command prompt to install Redux dev-tools.

npm install --save-dev redux-devtools

If you do not want to install Redux dev tools and integrate it into your project, you can install Redux DevTools Extension for Chrome and Firefox.

How Redux works

The way Redux works is simple. There is a central store that holds the entire state of the application.

Each component can access the stored state without having to send down props from one component to another.

There are three building parts: actions, store, and reducers.

Redux reduces the complexity of the code, by enforcing the restriction on how and when state update can happen. This way, managing updated states is easy. We already know about the restrictions as the three principles of Redux. Following diagram will help you understand Redux data flow better.

Data Flow
  • An action is dispatched when a user interacts with the application.
  • The root reducer function is called with the current state and the dispatched action. The root reducer may divide the task among smaller reducer functions, which ultimately returns a new state.
  • The store notifies the view by executing their callback functions.
  • The view can retrieve updated state and re-render again.

Redux – Store

A store is an immutable object tree in Redux. A store is a state container which holds the application’s state. Redux can have only a single store in your application. Whenever a store is created in Redux, you need to specify the reducer.

Let us see how we can create a store using the createStore method from Redux. One need to import the createStore package from the Redux library that supports the store creation process as shown below −

import { createStore } from 'redux';
import reducer from './reducers/reducer'
const store = createStore(reducer);

A createStore function can have three arguments. The following is the syntax −

createStore(reducer, [preloadedState], [enhancer])

A reducer is a function that returns the next state of app. A preloadedState is an optional argument and is the initial state of your app. An enhancer is also an optional argument. It will help you enhance store with third-party capabilities.

A store has three important methods as given below −

getState

It helps you retrieve the current state of your Redux store.

The syntax for getState is as follows −

store.getState()

dispatch

It allows you to dispatch an action to change a state in your application.

The syntax for dispatch is as follows −

store.dispatch({type:'ITEMS_REQUEST'})

subscribe

It helps you register a callback that Redux store will call when an action has been dispatched. As soon as the Redux state has been updated, the view will re-render automatically.

The syntax for dispatch is as follows −

store.subscribe(()=>{ console.log(store.getState());})

Note that subscribe function returns a function for unsubscribing the listener. To unsubscribe the listener, we can use the below code −

const unsubscribe = store.subscribe(()=>{console.log(store.getState());});
unsubscribe();

Redux – Actions

Actions are the only source of information for the store as per Redux official documentation. It carries a payload of information from your application to store.

As discussed earlier, actions are plain JavaScript object that must have a type attribute to indicate the type of action performed. It tells us what had happened. Types should be defined as string constants in your application as given below −

const ITEMS_REQUEST = 'ITEMS_REQUEST';

Apart from this type attribute, the structure of an action object is totally up to the developer. It is recommended to keep your action object as light as possible and pass only the necessary information.

To cause any change in the store, you need to dispatch an action first by using store.dispatch() function. The action object is as follows −

{ type: GET_ORDER_STATUS , payload: {orderId,userId } }
{ type: GET_WISHLIST_ITEMS, payload: userId }

Actions Creators

Action creators are the functions that encapsulate the process of creation of an action object. These functions simply return a plain Js object which is an action. It promotes writing clean code and helps to achieve reusability.

Let us learn about action creator which lets you dispatch an action, ‘ITEMS_REQUEST’ that requests for the product items list data from the server. Meanwhile, the isLoading state is made true in the reducer in ‘ITEMS_REQUEST’ action type to indicate that items are loading, and data is still not received from the server.

Initially, the isLoading state was false in the initialState object assuming nothing is loading. When data is received at browser, isLoading state will be returned as false in ‘ITEMS_REQUEST_SUCCESS’ action type in the corresponding reducer. This state can be used as a prop in react components to display loader/message on your page while the request for data is on. The action creator is as follows −

const ITEMS_REQUEST = ‘ITEMS_REQUEST’ ;
const ITEMS_REQUEST_SUCCESS = ‘ITEMS_REQUEST_SUCCESS’ ;

export function itemsRequest(bool,startIndex,endIndex) {
   let payload = {
      isLoading: bool,
      startIndex,
      endIndex
   }
   return {
      type: ITEMS_REQUEST,
      payload
   }
}

export function itemsRequestSuccess(bool) {
   return {
      type: ITEMS_REQUEST_SUCCESS,
      isLoading: bool,
   }
}

To invoke a dispatch function, you need to pass action as an argument to dispatch function.

dispatch(itemsRequest(true,1, 20));
dispatch(itemsRequestSuccess(false));

You can dispatch an action by directly using store.dispatch(). However, it is more likely that you access it with react-Redux helper method called connect(). You can also use bindActionCreators() method to bind many action creators with dispatch function.

Redux – Pure Functions

A function is a process which takes inputs called arguments, and produces some output known as return value. A function is called pure if it abides by the following rules −

  • A function returns the same result for same arguments.
  • Its evaluation has no side effects, i.e., it does not alter input data.
  • No mutation of local & global variables.
  • It does not depend on the external state like a global variable.

Let us take the example of a function which returns two times of the value passed as an input to the function. In general, it is written as, f(x) => x*2. If a function is called with an argument value 2, then the output would be 4, f(2) => 4.

Let us write the definition of the function in JavaScript as shown below −

const double = x => x*2; // es6 arrow function
console.log(double(2));  // 4

Here, double is a pure function.

As per the three principles in Redux, changes must be made by a pure function, i.e., reducer in Redux. Now, a question arises as to why a reducer must be a pure function.

Suppose, you want to dispatch an action whose type is ‘ADD_TO_CART_SUCCESS’ to add an item to your shopping cart application by clicking add to cart button.

Let us assume the reducer is adding an item to your cart as given below −

const initialState = {
   isAddedToCart: false;
}
const addToCartReducer = (state = initialState, action) => { //es6 arrow function
   switch (action.type) {
      case 'ADD_TO_CART_SUCCESS' :
         state.isAddedToCart = !state.isAddedToCart; //original object altered
         return state;
      default:
         return state;
   }
}
export default addToCartReducer ;

Let us suppose, isAddedToCart is a property on state object that allows you to decide when to disable ‘add to cart’ button for the item by returning a Boolean value ‘true or false’. This prevents user to add same product multiple times. Now, instead of returning a new object, we are mutating isAddedToCart prop on the state like above. Now if we try to add an item to cart, nothing happens. Add to cart button will not get disabled.

The reason for this behaviour is as follows −

Redux compares old and new objects by the memory location of both the objects. It expects a new object from reducer if any change has happened. And it also expects to get the old object back if no change occurs. In this case, it is the same. Due to this reason, Redux assumes that nothing has happened.

So, it is necessary for a reducer to be a pure function in Redux. The following is a way to write it without mutation −

const initialState = {
   isAddedToCart: false;
}
const addToCartReducer = (state = initialState, action) => { //es6 arrow function
   switch (action.type) {
      case 'ADD_TO_CART_SUCCESS' :
         return {
            ...state,
            isAddedToCart: !state.isAddedToCart
         }
      default:
         return state;
   }
}
export default addToCartReducer;

Redux – Reducers

Reducers are a pure function in Redux.

Pure functions are predictable.

Reducers are the only way to change states in Redux.

It is the only place where you can write logic and calculations.

Reducer function will accept the previous state of app and action being dispatched, calculate the next state and returns the new object.

The following few things should never be performed inside the reducer −

  • Mutation of functions arguments
  • API calls & routing logic
  • Calling non-pure function e.g. Math.random()

The following is the syntax of a reducer −

(state,action) => newState

Let us continue the example of showing the list of product items on a web page, discussed in the action creators module. Let us see below how to write its reducer.

const initialState = {
   isLoading: false,
   items: []
};

const reducer = (state = initialState, action) => {
   switch (action.type) {
      case 'ITEMS_REQUEST':
         return Object.assign({}, state, {
            isLoading: action.payload.isLoading
         })
      case ‘ITEMS_REQUEST_SUCCESS':
         return Object.assign({}, state, {
            items: state.items.concat(action.items),
            isLoading: action.isLoading
         })
      default:
         return state;
   }
}
export default reducer;

Firstly, if you do not set state to ‘initialState’, Redux calls reducer with the undefined state. In this code example, concat() function of JavaScript is used in ‘ITEMS_REQUEST_SUCCESS’, which does not change the existing array; instead returns a new array.

In this way, you can avoid mutation of the state. Never write directly to the state. In ‘ITEMS_REQUEST’, we have to set the state value from the action received.

It is already discussed that we can write our logic in reducer and can split it on the logical data basis. Let us see how we can split reducers and combine them together as root reducer when dealing with a large application.

Suppose, we want to design a web page where a user can access product order status and see wishlist information. We can separate the logic in different reducers files, and make them work independently. Let us assume that GET_ORDER_STATUS action is dispatched to get the status of order corresponding to some order id and user id.

/reducer/orderStatusReducer.js
import { GET_ORDER_STATUS } from ‘../constants/appConstant’;

export default function (state = {} , action) {
   switch(action.type) {
      case GET_ORDER_STATUS:
         return { ...state, orderStatusData: action.payload.orderStatus };
      default:
         return state;
   }
}

Similarly, assume GET_WISHLIST_ITEMS action is dispatched to get the user’s wishlist information respective of a user.

/reducer/getWishlistDataReducer.js
import { GET_WISHLIST_ITEMS } from ‘../constants/appConstant’;

export default function (state = {}, action) {
   switch(action.type) {
      case GET_WISHLIST_ITEMS:
         return { ...state, wishlistData: action.payload.wishlistData };
      default:
         return state;
   }
}

Now, we can combine both reducers by using Redux combineReducers utility. The combineReducers generate a function which returns an object whose values are different reducer functions. You can import all the reducers in index reducer file and combine them together as an object with their respective names.

/reducer/index.js
import { combineReducers } from ‘redux’;
import OrderStatusReducer from ‘./orderStatusReducer’;
import GetWishlistDataReducer from ‘./getWishlistDataReducer’;

const rootReducer = combineReducers ({
   orderStatusReducer: OrderStatusReducer,
   getWishlistDataReducer: GetWishlistDataReducer
});
export default rootReducer;

Now, you can pass this rootReducer to the createStore method as follows −

const store = createStore(rootReducer);

Redux – Middleware

Redux itself is synchronous, so how the async operations like network request work with Redux? Here middlewares come handy. As discussed earlier, reducers are the place where all the execution logic is written. Reducer has nothing to do with who performs it, how much time it is taking or logging the state of the app before and after the action is dispatched.

In this case, Redux middleware function provides a medium to interact with dispatched action before they reach the reducer. Customized middleware functions can be created by writing high order functions (a function that returns another function), which wraps around some logic. Multiple middlewares can be combined together to add new functionality, and each middleware requires no knowledge of what came before and after. You can imagine middlewares somewhere between action dispatched and reducer.

Commonly, middlewares are used to deal with asynchronous actions in your app. Redux provides with API called applyMiddleware which allows us to use custom middleware as well as Redux middlewares like redux-thunk and redux-promise. It applies middlewares to store. The syntax of using applyMiddleware API is −

applyMiddleware(...middleware)

And this can be applied to store as follows −

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const store = createStore(rootReducer, applyMiddleware(thunk));
Middleware

Middlewares will let you write an action dispatcher which returns a function instead of an action object. Example for the same is shown below −

function getUser() {
   return function() {
      return axios.get('/get_user_details');
   };
}

Conditional dispatch can be written inside middleware. Each middleware receives store’s dispatch so that they can dispatch new action, and getState functions as arguments so that they can access the current state and return a function. Any return value from an inner function will be available as the value of dispatch function itself.

The following is the syntax of a middleware −

({ getState, dispatch }) => next => action

The getState function is useful to decide whether new data is to be fetched or cache result should get returned, depending upon the current state.

Let us see an example of a custom middleware logger function. It simply logs the action and new state.

import { createStore, applyMiddleware } from 'redux'
import userLogin from './reducers'

function logger({ getState }) {
   return next => action => {
      console.log(‘action’, action);
      const returnVal = next(action);
      console.log('state when action is dispatched', getState()); 
      return returnVal;
   }
}

Now apply the logger middleware to the store by writing the following line of code −

const store = createStore(userLogin , initialState=[ ] , applyMiddleware(logger));

Dispatch an action to check the action dispatched and new state using the below code −

store.dispatch({
   type: 'ITEMS_REQUEST', 
	isLoading: true
})

Another example of middleware where you can handle when to show or hide the loader is given below. This middleware shows the loader when you are requesting any resource and hides it when resource request has been completed.

import isPromise from 'is-promise';

function loaderHandler({ dispatch }) {
   return next => action => {
      if (isPromise(action)) {
         dispatch({ type: 'SHOW_LOADER' });
         action
            .then(() => dispatch({ type: 'HIDE_LOADER' }))
            .catch(() => dispatch({ type: 'HIDE_LOADER' }));
      }
      return next(action);
   };
}
const store = createStore(
   userLogin , initialState = [ ] , 
   applyMiddleware(loaderHandler)
);

Example

Using Functional Component

//store.js
import { createStore } from 'redux';

const counterReducer = (state = { counter: 0 }, action) => {
 
 if (action.type === 'increment') {
    return {
      counter: state.counter + 1,
    };
  }

  if (action.type === 'increase') {
    return {
      counter: state.counter + action.amount,
    };
  }

  if (action.type === 'decrement') {
    return {
      counter: state.counter - 1,
    };
  }

  return state;
};

const store = createStore(counterReducer);

export default store;

//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

import './index.css';
import App from './App';
import store from './store/store';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

//Counter.js
import { useSelector, useDispatch } from 'react-redux';

import classes from './Counter.module.css';

const Counter = () => {
  
  const dispatch = useDispatch();
  const counter = useSelector((state) => state.counter);

  const incrementHandler = () => {
    dispatch({ type: 'increment' });
  };

  const increaseHandler = () => {
    dispatch({ type: 'increase', amount: 10 });
  };

  const decrementHandler = () => {
    dispatch({ type: 'decrement' });
  };

  const toggleCounterHandler = () => {};

  return (
    <main className={classes.counter}>
      <h1>Redux Counter</h1>
      <div className={classes.value}>{counter}</div>
      <div>
        <button onClick={incrementHandler}>Increment</button>
        <button onClick={increaseHandler}>Increase by 10</button>
        <button onClick={decrementHandler}>Decrement</button>
      </div>
      <button onClick={toggleCounterHandler}>Toggle Counter</button>
    </main>
  );
};

export default Counter;

Using Class-based Components

import classes from './Counter.module.css';
import {Component} from 'react';
import {connect} from 'react-redux';

class Counter extends Component {
  
incrementHandler() {
    this.props.increment();
  }

  decrementHandler() {
    this.props.decrement();
  }

  toggleCounterHandler() {}

  render() {
    return (
      <main className={classes.counter}>
        <h1>Redux Counter</h1>
        <div className={classes.value}>{this.props.counter}</div>
        <div>
          <button onClick={this.incrementHandler.bind(this)}>Increment</button>
          <button onClick={this.decrementHandler.bind(this)}>Decrement</button>
        </div>
        <button onClick={this.toggleCounterHandler}>Toggle Counter</button>
      </main>
    );
  }
}

const mapStateToProps = state => {
  return {
    counter: state.counter
  };
}

const mapDispatchToProps = dispatch => {
  return {
    increment: () => dispatch({ type: 'INCREMENT' }),
    decrement: () => dispatch({ type: 'DECREMENT' }),
  }
};

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

Conclusion

This image has an empty alt attribute; its file name is 68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6d656469612d702e736c69642e65732f75706c6f6164732f3336343831322f696d616765732f323438343535322f415243482d5265647578322d7265616c2e676966