Redux - Powerful State Management Tool

Redux - Powerful State Management Tool

ยท

7 min read

Formal Definitions of Redux Library:

  • Redux is a predictable state container for javascript applications.

  • Redux is a predictable state management library for javascript applications.

Let's understand the definition by breaking it into smaller components.

  • It is used for javascript-based applications.

One thing to keep in mind is Redux is not tied to React, it can be used with any javascript application including:

  • React Js

  • Angular Js

  • Vue Js

  • Plain vanilla javascript

  • It is predictable.

Redux being a state container stores the state of an application and the state can change depending on various requirements. All state transitions in redux are predictable and it is possible to keep track of the changes.

  • It is a state container which holds the state of the application.

Redux stores the state of the application. A state is a JavaScript object that stores the dynamic data of a component. Let's consider a React Js application where we have a login component so its form may look like this:

Login Form Component
state = {
username:"",
password:"",
rememberMe:false
}
Product Component
state = {
productId:"",
productName:"",
productPrice:"",
images:[ { imgUrl:"" }]
}

Redux will store and manage the application state. It is a javascript library used widely for state management.

Three core concepts of Redux

Let's consider a cake shop to understand this scenario. The cake shop has a shopkeeper who has access to all the cakes and the record book which contains the number of cakes present in the shop.

๐Ÿค“

Now let's consider a customer who wants to buy a cake from the shop, but he doesn't have direct access to the cakes in the shop and he needs to communicate with the shopkeeper if he wants to have a cake. He needs to give instructions to the shopkeeper "Give me a cake". The shopkeeper will listen to the instruction and get him a cake and decrement the cake count by 1 in the record book.

What did we understand from the above example?

  • The cake shop contains cake safely stored.

  • There is a shopkeeper in the shop who only has the access to cakes and customers don't have direct access to cakes.

  • If the customer wants to buy a cake, he needs to instruct the shopkeeper.

  • The shopkeeper gives the cake to the customer and updates the record book.

We have three entities to note here:

  • Shop

  • Shopkeeper

  • Customer's Intension to Buy Cake

Let's discuss redux now ๐Ÿ˜Œ,

Cake Shop ScenarioRedux ScenarioPurpose
ShopStoreHolds the state of the application.
ShopkeeperReducer FunctionTies the store to the actions.
Intention to Buy CakeActionInstructs the reducer to perform a purpose

So, three core concepts of Redux:

  • A store that holds the entire state of your application.

  • An action that describes the changes in the state of the application.

  • A reducer that actually carries out state transitions based on actions.

Three Principles of Redux

  • The state of our whole application is stored in an object tree within a single store.

It means maintaining our application state in a single object which would be managed by the Redux store.

// state of application
const initialState = {
    numOfCakes:10,
    numOfIcecreams:20
}
  • The only way to change the state is to emit an action, an object describing what happened.

To update the state of our application, we need to let Redux know about that with an action. It's not allowed to directly update the state object.

const BUY_CAKE = 'BUY_CAKE';  // action
const BUY_ICECREAM = 'BUY_ICECREAM'; 
// plain action object with type property 
{
        type:BUY_CAKE,
        info:'action to buy cake'
}
// action creator - a function that returns the state object
function buyCake(){
    return {
        type:BUY_CAKE,
        info:'action to buy cake'
    }
}
function buyIcecream(){
    return {
        type:BUY_ICECREAM,
        info:'action to buy icecream'
    }
}
  • To specify how the state tree is transformed by actions, we write pure reducers.

Pure reducers are pure basically pure functions which take the state and action to be performed as parameters and return the updated state.

// (previousState,action) => newState
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
    }
}

The basic flow of Redux application:

Redux Store

  • There is one store for the entire application.

Responsibilities of Store:

  • It holds the application state.

  • Allows access to application state via getState().

  • Allows the state to be updated via dispatch(action).

  • Registers listeners via subscribe(listener).

  • Handles unregistering of listeners via the function returned by subscribe(listener).

Let's create a store of our application:

const redux = require('redux');
const createStore = redux.createStore; // redux store

const store = createStore(reducer); // passing reducer to create store

Let's use the getState() method to access the state of our application.

console.log("Initial State: " , store.getState());
// This will return the initial state of the application

Now, we need to subscribe to the store to get updated whenever the state changes.

const unsubscribe = store.subscribe(() => console.log('Updated state: ' ,store.getState()));

Now, we can unsubscribe to the store by calling the function returned by the subscribe method. We store that function in the unsubscribe variable.

Finally, 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

Asynchronous Actions

Async actions are used when we want to make async API calls to fetch data from an end point and use that data in our application. Basically fetching information from an api end point asynchronously.

Now we will develop an application that fetches users from an API endpoint and stores the data in Redux store.

Let's code our application...

State

// state of application
const initalState = {
    loading:true,
    users:[],
    error:''
}

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.

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!!!

Now, in the next article we will discuss about how to use redux with react and make applications with effective state management properties. Hope it helps!!!

ย