Book Image

Redux Quick Start Guide

By : James Lee, Tao Wei, Suresh Kumar Mukhiya
Book Image

Redux Quick Start Guide

By: James Lee, Tao Wei, Suresh Kumar Mukhiya

Overview of this book

Starting with a detailed overview of Redux, we will follow the test-driven development (TDD) approach to develop single-page applications. We will set up JEST for testing and use JEST to test React, Redux, Redux-Sage, Reducers, and other components. We will then add important middleware and set up immutableJS in our application. We will use common data structures such as Map, List, Set, and OrderedList from the immutableJS framework. We will then add user interfaces using ReactJS, Redux-Form, and Ant Design. We will explore the use of react-router-dom and its functions. We will create a list of routes that we will need in order to create our application, and explore routing on the server site and create the required routes for our application. We will then debug our application and integrate Redux Dev tools. We will then set up our API server and create the API required for our application. We will dive into a modern approach to structuring our server site components in terms of Model, Controller, Helper functions, and utilities functions. We will explore the use of NodeJS with Express to build the REST API components. Finally, we will venture into the possibilities of extending the application for further research, including deployment and optimization.
Table of Contents (16 chapters)
Title Page
Copyright and Credits
Dedication
About Packt
Contributors
Preface
Index

Elements of Redux


To understand Redux, we need to understand its components. There are four main elements of Redux; let's discuss each of them, one by one.

Actions

Actions are simply JavaScript objects describing the changes in the state of the application. To be specific, they are payloads of information that transfer data from our application to the state. Does this not make sense to you? No problem. Let's look at an example use case. Suppose that we need to add a doctor's information to our hospital management system:

const ADD_NEW_DOCTOR_REQUEST = "ADD_NEW_DOCTOR_REQUEST"

It isn't rocket science, right? It's just a simple, constant ADD_DOCTOR_REQUEST. Now, let's create an object:

{
 type: ADD_NEW_DOCTOR_REQUEST,
 data: {
   name: ‘Dr. Yoshmi Mukhiya’,
   age: 22,
   department: ‘Mental Health’,
   telecom: ‘99999999’
 }
}

This is a simple, plain JavaScript object, and it is referred to as an action. An action must have the type property that defines the type of action to be performed. In this use case, the action is adding an action. The type is basically a string constant. In any web application, there are a multitude of actions required. So, the general (and most common) trend is to separate these actions into separate files and import them into the required place.

Now, let's assume that we need to delete a doctor's record from our app. We should be able to create an action object easily, as follows:

{
 type: 'DELETE_DOCTOR_REQUEST',
 identifier: 201,
}

Now, go ahead and create the actions for the following:

  1. Adding a user to the hospital management system
  2. Deleting a user from the hospital management system
  3. Updating a user

Action creators

JavaScript functions that take some arguments and return actions are action creators. Let's look at an action creator function for adding a new doctor to the application:

function addNewDoctor(data) {
 return {
   type: ADD_NEW_DOCTOR_REQUEST,
   data
 };
}

Now, you can think of a function that you might need for deleting a record, as follows:

function deleteDoctor(identifier) {
 return {
   type: "DELETE_DOCTOR_REQUEST",
   identifier
 };
}

Before we move on to reducers, let's make one more action creator for authentication. Generally, to authenticate, we use an email and password. So, in order to authenticate (or deauthenticate) we need to define actions. Please note that the actions that we define will be used in our project for a hospital management system. Our action for authentication could look something like the following:

export const authenticate = (credentials) => ({
 type: "AUTHENTICATE",
 payload: credentials
});
export const deauthenticate = () => ({
 type: "DEAUTHENTICATE"
});

Similarly, let's create action creators for registering a user. When we register a user, we are likely to have a request, a success, or a failure. Based on these three states, we can create the action creators, as follows:

export const onRegisterRequest = user => ({ type: REGISTER_REQUEST, user });

export const onRegisterSuccess = user => ({ type: REGISTER_SUCCESS, user });

export const onRegisterFailure = message => ({
  type: REGISTER_FAILURE,
  message,
});

Reducers

JavaScript functions that take actions and states as input and return the new states are reducers. Well, if this is confusing, try to keep in mind that the action only describes what happened, not how the application state transforms.

It is very important to understand the reducer function. Let's consider our hospital management system. Our application's state can look like the following:

{
 doctors: [
   {
     name: "John Doe",
     department: "Radiology",
     address: "Kathmandu, 4017, Nepal",
     telecom: "999-999-999"
   },
   {
     name: "Ola Nordmann",
     department: "General Physician",
     address: "Kong Oscarsgate 29, 5017, Bergen, Norway",
     telecom: "111-111-1111"
   }
 ];
}

When creating a reducer function, it is important that we remember the reducer principle: it must be a pure function. It should just take the action and return a new state, with no side effects, no mutations, and no API calls.

Let's consider another example of a content management system. In a normal CMS, we have posts and categories. So, our state at an instance could look like the following:

{
 posts: [
   { user: 'John Doe', category: 'Practitioner', text: 'This is the first post about Practitioner.' },
   { user: 'Ola Nordmann', category: 'Patients', text: 'This is the first post about Patients.' }
 ],
 filter: ‘Patients’
}

There's nothing complicated here, right? Now, let's start to write our reducer function for both use cases: our CMS use case and our hospital management system use case.

We will start by defining an initial state. Let's initiate our initial state by creating an empty object with an array of empty doctors:

const initialState = {
 doctors: []
};

In any database, there is a need for creating, updating, reading, and deleting resources. Similarly, in the hospital management system, we need to read a doctor's record, create a new record, update it, or delete it. Hence, we are likely to have multiple action objects defined, as we mentioned in the preceding section.

This introduces a requirement to handle reducer functions for each of the actions. We can create a single reducer function to handle a similar scenario, and make use of the switch case to handle multiple action types:

import {
 ADD_NEW_DOCTOR_REQUEST,
} from './actions'

function addDoctor(state = initialState, action) {
 switch (action.type) {
   case ADD_NEW_DOCTOR_REQUEST:
     return Object.assign({}, state, {
       doctors: [
         ...state.doctors,
         {
           name: action.name,
           age: action.age,
           department: action.department,
           telecom: action.telecom
         }
       ]
     });
   default:
     return state;
 }
}

In the preceding snippet, we have defined ADD_NEW_DOCTOR_REQUEST in the actions. We can check the action type for deleting the doctor's record. Go ahead and add a reducer use case for deleting a doctor.

Now, your task is to check the initial state of the CMS system and write reducer functions for CREATE_POST, EDIT_POST, and SET_FILTER. Once you have finished writing the reducer function, it should look something like the following:

import { CREATE_POST, EDIT_POST, SET_FILTER } from './actionTypes'

function postsReducer (state = [], action) {
 switch (action.type) {
   case CREATE_POST: {
     const { type, ...post } = action
     return [ ...state, post ]
   }

   case EDIT_POST: {
     const { type, id, ...newPost } = action
     return state.map((oldPost, index) =>
       action.id === index
         ? { ...oldPost, ...newPost }
         : oldPost
     )
   }

   default:
     return state
 }
}

Store  

The store stores all of the states of the application. Hence, it is sometimes referred to as the heart of the application. The most important point to note is that there is a single store in the entire application. To create a store, we can use the createStore function provided by Redux:

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

The methods for stores will be explained in the following subsections.

getState()

The getState() method gives you the current state of any application, which is equal to the last value returned by the application's reducer.

dispatch(action)

As the name suggests, dispatch(action) only dispatches the action. The main point to keep in mind is that this is the single way to modify the state.

subscribe(listeners)

The subscribe(listeners) method adds a change listener, which is called any time an action is dispatched, and some part of the state tree may potentially have changed.

replaceReducer(nextReducer)

The replaceReducer(nextReducer) method replaces the reducer that's currently used by the store to calculate the state. It is an advanced API, and may not be required for normal use cases.