We now have a youtube channel. Subscribe!

ReactJS | Redux

ReactJS | Redux


Hello folks! welcome back to a new section of our tutorial on ReactJS. In this section of our tutorial on ReactJS, we will be studying about ReactJS Redux.

ReactJS redux is the official advanced state management library for React. As we learnt in our previous tutorial, React only supports component level state management. In a big and complex application, a large number of components are used. React recommends moving the state to the top level component and pass the state to the nested component via properties. It helps to certain extent but it becomes complex when the components increases.

React redux comes in and helps to maintain state at the application level. ReactJS redux enables any component to access the state at any time. Also, it enables any component to change the state of the application at any time.

Let's learn how to write a React application using React redux in this tutorial.

Concepts

React redux maintains the application state in a single place called Rredux Store. React component can get the latest state from the store and also change the state at any time. Redux provides a simple process to get and set the present state of the application and it involves below concepts.

Store

The central place for storing the state of the application.

Actions

Action is plain object with the type of action to be performed and the input necessary to do the action. For example, action to add an item in the store has ADD_ITEM as type and an object with item's details as payload. The item can be represented as -

{ 
   type: 'ADD_ITEM', 
   payload: { name: '..', ... }
}

Reducers

Reducers are pure functions used to create a new state based on existing state and the current action. It returns the newly created state. For example, in add item scenario, it creates a new item list and merges the item from the state and new item and returns the newly created list.

Action Creators

Action creator creates an action with proper action type and data required for the action and return the action. For example, addItem action creator returns the below object -

{ 
   type: 'ADD_ITEM', 
   payload: { name: '..', ... }
}

Component

Component can connect to the store to get the current state and dispatch action to the store so that the store executes the action and updates its current state.


The workflow of a typical redux store can be represented as shown below -

Redux_store

  • React component subscribes to the store and get the latest state during initialization of the application.
  • To change the state, react component creates needed action and dispatches the action.
  • Reducer creates a new state based on the action and then returns it. It stores updates itself with the new state.
  • Once the state changes, store sends the updated state to all its subscribed components.

Redux API

Redux provides a single api, connect which will connect a component to the store and lets the component to get and set the state of the store.

The syntax of the connect API is as follows -

function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)

All parameters are optional and it returns a (HOC) higer order component. Higher order component (HOC) is a function that wraps a component and return a new component.

let hoc = connect(mapStateToProps, mapDispatchToProps) 
let connectedComponent = hoc(component)

Let's see the first two parameters which will be enough for most cases.

  • mapStateToProps - Accepts a function with below syntax.
(state, ownProps?) => Object

Here, state refers current state of the store and Object refers the new props of the react component. It get called whenever the state of the store is updated.

(state) => { prop1: this.state.anyvalue }

  • mapDispatchToProps - Accepts a function with below syntax.
Object | (dispatch, ownProps?) => Object

Here, dispatch refers the dispatch object that is used to dispatch action in the redux store and Object refer one or more dispatch functions as props of the component.

(dispatch) => {
   addDispatcher: (dispatch) => dispatch({ type: 'ADD_ITEM', payload: { } }),
   removeispatcher: (dispatch) => dispatch({ type: 'REMOVE_ITEM', payload: { } }),
}


Provider Component

React redux provides a Provider component and its only purpose is to make Redux store available to all its nested components that's connected to store using connect API. The sample code is given below -

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { App } from './App'
import createStore from './createReduxStore'

const store = createStore()

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

All components inside the App component can get access to the Redux store by using connect API.

Working Example

We will be recreating our expense manager application and then using the React redux concept to maintain the application state.

First, create a new react application, react-message-app using the Create React App or Rollup bundler by following instructions in Creating a React application tutorial.

Next, install Redux and React redux library.

npm install redux react-redux --save

Next, install uuid library to generate unique identifier for new expenses.

npm install uuid --save

Next, open the application in your favorite editor.

Next, create an src folder beneath the root directory of the application.

Next, create actions folder under src folder.

Next, create a file, types.js under src/actions folder and start editing.

Add two action type, one for add expense and one for remove expense.

export const DELETE_EXPENSE = 'DELETE_EXPENSE';

Next, create a file, index.js under src/actions folder to add action and start editing.

Next, import uuid to create unique identifier.

import { v4 as uuidv4 } from 'uuid';

Next, import action types.

import { ADD_EXPENSE, DELETE_EXPENSE } from './types';

Add a new function to return the action type for adding an expense and export it.

export const addExpense = ({ name, amount, spendDate, category }) => ({
   type: ADD_EXPENSE,
   payload: {
      id: uuidv4(),
      name,
      amount,
      spendDate,
      category
   }
});

Here, the function expects expense object & a return action type of ADD_EXPENSE along with a payload of expense information.

Add a new function to return the action type for deleting an expense and export it.

export const deleteExpense = id => ({
   type: DELETE_EXPENSE,
   payload: {
      id
   }
});

Here, the function expects id of the expense item to be deleted and return action type of DELETE_EXPENSE along with a payload of expense id.

The full code of the action is given below -

import { v4 as uuidv4 } from 'uuid';
import { ADD_EXPENSE, DELETE_EXPENSE } from './types';

export const addExpense = ({ name, amount, spendDate, category }) => ({
   type: ADD_EXPENSE,
   payload: {
      id: uuidv4(),
      name,
      amount,
      spendDate,
      category
   }
});
export const deleteExpense = id => ({
   type: DELETE_EXPENSE,
   payload: {
      id
   }
});


Next, create a new folder, reducers beneath src folder.

Next, create a file, index.js under src/reducer to write reducer function and start editing.

Next, import the action types.

import { ADD_EXPENSE, DELETE_EXPENSE } from '../actions/types';

Add a function, expensesReducer to do the actual feature of adding and updating the expenses in the redux store.

export default function expensesReducer(state = [], action) {
   switch (action.type) {
      case ADD_EXPENSE:
         return [...state, action.payload];
      case DELETE_EXPENSE:
         return state.filter(expense => expense.id !== action.payload.id);
      default:
         return state;
   }
}

The complete code of the reducer is given below -

import { ADD_EXPENSE, DELETE_EXPENSE } from '../actions/types';

export default function expensesReducer(state = [], action) {
   switch (action.type) {
      case ADD_EXPENSE:
         return [...state, action.payload];
      case DELETE_EXPENSE:
         return state.filter(expense => expense.id !== action.payload.id);
      default:
         return state;
   }
}

Here, the reducer checks the action type and executes the relevant code.

Next, create a component folder under src folder.

Next, create a file, ExpenseEntryItemList.css beneath src/component folder and then add generic styles for the HTML tables.

html {
   font-family: sans-serif;
}
table {
   border-collapse: collapse;
   border: 2px solid rgb(200,200,200);
   letter-spacing: 1px;
   font-size: 0.8rem;
}
td, th {
   border: 1px solid rgb(190,190,190);
   padding: 10px 20px;
}
th {
   background-color: rgb(235,235,235);
}
td, th {
   text-align: left;
}
tr:nth-child(even) td {
   background-color: rgb(250,250,250);
}
tr:nth-child(odd) td {
   background-color: rgb(245,245,245);
}
caption {
   padding: 10px;
}
tr.highlight td { 
   background-color: #a6a8bd;
}

Next, create a file, ExpenseEntryItemList.js under src/component folder and then start editing.

Next, import React and React redux library.

import React from 'react'; 
import { connect } from 'react-redux';

Next, import ExpenseEntryItemList.css file.

import './ExpenseEntryItemList.css';

Next, import action creators.

import { deleteExpense } from '../actions'; 
import { addExpense } from '../actions';

Next, create a class, ExpenseEntryItemList and call constructor with props.

class ExpenseEntryItemList extends React.Component {
   constructor(props) {
      super(props);
   }
}

Next, create a mapStateToProps function.

const mapStateToProps = state => {
   return {
      expenses: state
   };
};

Here, we copied the input state to expenses props of the component.

Next, create mapDispatchToProps function.

const mapDispatchToProps = dispatch => {
   return {
      onAddExpense: expense => {
         dispatch(addExpense(expense));
      },
      onDelete: id => {
         dispatch(deleteExpense(id));
      }
   };
};

Here, we have created two functions, one to dispatch add expense function and another to dispatch delete expense function and we mapped those functions to the props of the components.

Next, export the component using connect api.

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

The component gets three new properties given below -

  • expenses - list of expense
  • onAddExpense - function to dispatch the addExpense function
  • onDelete - function to dispatch the deleteExpense function

Next, add few expense into the redux store in the constructor using the onAddExpense property.

if (this.props.expenses.length == 0)
{
   const items = [
      { id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
      { id: 2, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
      { id: 3, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
      { id: 4, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
      { id: 5, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
      { id: 6, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
      { id: 7, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
      { id: 8, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
      { id: 9, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
      { id: 10, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
   ]
   items.forEach((item) => {
      this.props.onAddExpense(
         { 
            name: item.name, 
            amount: item.amount, 
            spendDate: item.spendDate, 
            category: item.category 
         }
      );
   })
}


Add an event handler to delete the expense item using expense id.

handleDelete = (id,e) => {
   e.preventDefault();
   this.props.onDelete(id);
}

Here, the event handler calls the onDelete dispatch, which calls deleteExpense along with the expense id.

Add a method to calculate the total amount of all expenses.

getTotal() {
   let total = 0;
   for (var i = 0; i < this.props.expenses.length; i++) {
      total += this.props.expenses[i].amount
   }
   return total;
}

Next, add render() method and then list the expense item in the tabular format.

render() {
   const lists = this.props.expenses.map(
      (item) =>
      <tr key={item.id}>
         <td>{item.name}</td>
         <td>{item.amount}</td>
         <td>{new Date(item.spendDate).toDateString()}</td>
         <td>{item.category}</td>
         <td><a href="#"
            onClick={(e) => this.handleDelete(item.id, e)}>Remove</a></td>
      </tr>
   );
   return (
      <div>
         <table>
            <thead>
               <tr>
                  <th>Item</th>
                  <th>Amount</th>
                  <th>Date</th>
                  <th>Category</th>
                  <th>Remove</th>
               </tr>
            </thead>
            <tbody>
               {lists}
               <tr>
                  <td colSpan="1" style={{ textAlign: "right" }}>Total Amount</td>
                  <td colSpan="4" style={{ textAlign: "left" }}>
                     {this.getTotal()}
                  </td>
               </tr>
            </tbody>
         </table>
      </div>
   );
}

Here, we set the event handler, handleDelete to remove the expense from the store.

The full code of the ExpenseEntryItemList component is as follows -

import React from 'react';
import { connect } from 'react-redux';
import './ExpenseEntryItemList.css';
import { deleteExpense } from '../actions';
import { addExpense } from '../actions';

class ExpenseEntryItemList extends React.Component {
   constructor(props) {
      super(props);

      if (this.props.expenses.length == 0){
         const items = [
            { id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
            { id: 2, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
            { id: 3, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
            { id: 4, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
            { id: 5, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
            { id: 6, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
            { id: 7, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
            { id: 8, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
            { id: 9, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
            { id: 10, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
         ]
         items.forEach((item) => {
            this.props.onAddExpense(
               { 
                  name: item.name, 
                  amount: item.amount, 
                  spendDate: item.spendDate, 
                  category: item.category 
               }
            );
         })
      }
   }
   handleDelete = (id,e) => {
      e.preventDefault();
      this.props.onDelete(id);
   }
   getTotal() {
      let total = 0;
      for (var i = 0; i < this.props.expenses.length; i++) {
         total += this.props.expenses[i].amount
      }
      return total;
   }
   render() {
      const lists = this.props.expenses.map((item) =>
         <tr key={item.id}>
            <td>{item.name}</td>
            <td>{item.amount}</td>
            <td>{new Date(item.spendDate).toDateString()}</td>
            <td>{item.category}</td>
            <td><a href="#"
               onClick={(e) => this.handleDelete(item.id, e)}>Remove</a></td>
         </tr>
      );
      return (
         <div>
            <table>
               <thead>
                  <tr>
                     <th>Item</th>
                     <th>Amount</th>
                     <th>Date</th>
                     <th>Category</th>
                     <th>Remove</th>
                  </tr>
               </thead>
               <tbody>
                  {lists}
                  <tr>
                     <td colSpan="1" style={{ textAlign: "right" }}>Total Amount</td>
                     <td colSpan="4" style={{ textAlign: "left" }}>
                        {this.getTotal()}
                     </td>
                  </tr>
               </tbody>
            </table>
         </div>
      );
   }
}
const mapStateToProps = state => {
   return {
      expenses: state
   };
};
const mapDispatchToProps = dispatch => {
   return {
      onAddExpense: expense => {
         dispatch(addExpense(expense));
      },
      onDelete: id => {
         dispatch(deleteExpense(id));
      }
   };
};
export default connect(
   mapStateToProps,
   mapDispatchToProps
)(ExpenseEntryItemList);

Create a file, App.js beneath src/component folder and then use ExpenseEntryItemList component.

import React, { Component } from 'react';
import ExpenseEntryItemList from './ExpenseEntryItemList';

class App extends Component {
   render() {
      return (
         <div>
            <ExpenseEntryItemList />
         </div>
      );
   }
}
export default App;

Next, create a file, index.js under src folder.

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './reducers';
import App from './components/App';

const store = createStore(rootReducer);

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

Here,

  • Create a store using createStore by attaching the reducer.
  • Used Provider component from React redux library and then set the store as props, which enables all of the nested components to connect to store using connect api.

Finally, create a public folder under the root folder and create an index.html file.

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="utf-8">
      <title>React Containment App</title>
   </head>
   <body>
      <div id="root"></div>
      <script type="text/JavaScript" src="./index.js"></script>
   </body>
</html>

Next, serve the application making use of npm command.

npm start

Next, open the web browser and then enter http://localhost:3000 in the address bar and press enter.

Clicking on the remove link will remove the item from the redux store.

Redux


Alright guys! This is where we are going to be rounding up for this tutorial. In our next tutorial, we are going to be studying about ReactJS Animation.

Feel free to ask your questions where necessary and we will attend to them as soon as possible. If this tutorial was helpful to you, you can use the share button to share this tutorial.

Follow us on our various social media platforms to stay updated with our latest tutorials. You can also subscribe to our newsletter in order to get our tutorials delivered directly to your emails.

Thanks for reading and bye for now.

Post a Comment

Hello dear readers! Please kindly try your best to make sure your comments comply with our comment policy guidelines. You can visit our comment policy page to view these guidelines which are clearly stated. Thank you.
© 2023 ‧ WebDesignTutorialz. All rights reserved. Developed by Jago Desain