ReactJS - State management



We are going to do below action to manage our redux store.

  • Fetching the expenses from server through async fetch api and set it in Redux store.

  • Add new expense to the server through async fetch programming and set add the new expense in the Redux store.

  • Delete existing expense from the server through async fetch api and update the Redux store.

Let us create action types, action creator, actions and reducers for managing the Redux state.

Create a folder actions under src folder.

Next, create a file, types.js to create action types.

export const LIST_EXPENSE_STARTED = 'LIST_EXPENSE_STARTED';
export const LIST_EXPENSE_SUCCESS = 'LIST_EXPENSE_SUCCESS';
export const LIST_EXPENSE_FAILURE = 'LIST_EXPENSE_FAILURE';

export const ADD_EXPENSE_STARTED = 'ADD_EXPENSE_STARTED';
export const ADD_EXPENSE_SUCCESS = 'ADD_EXPENSE_SUCCESS';
export const ADD_EXPENSE_FAILURE = 'ADD_EXPENSE_FAILURE';

export const DELETE_EXPENSE_STARTED = 'DELETE_EXPENSE_STARTED';
export const DELETE_EXPENSE_SUCCESS = 'DELETE_EXPENSE_SUCCESS';
export const DELETE_EXPENSE_FAILURE = 'DELETE_EXPENSE_FAILURE';

Next, create a file, index.js under actions folder to create action creators.

import {
   LIST_EXPENSE_STARTED, LIST_EXPENSE_SUCCESS, LIST_EXPENSE_FAILURE,
   ADD_EXPENSE_STARTED, ADD_EXPENSE_SUCCESS, ADD_EXPENSE_FAILURE,
   DELETE_EXPENSE_STARTED, DELETE_EXPENSE_SUCCESS, DELETE_EXPENSE_FAILURE,
} from "./types";
export const getExpenseListStarted = () => {
   return {
      type: LIST_EXPENSE_STARTED
   }
}
export const getExpenseListSuccess = data => {
   return {
      type: LIST_EXPENSE_SUCCESS,
      payload: {
         data
      }
   }
}
export const getExpenseListFailure = error => {
   return {
      type: LIST_EXPENSE_FAILURE,
      payload: {
         error
      }
   }
}
export const addExpenseStarted = () => {
   return {
      type: ADD_EXPENSE_STARTED
   }
}
export const addExpenseSuccess = data => {
   return {
      type: ADD_EXPENSE_SUCCESS,
      payload: {
         data
      }
   }
}
export const addExpenseFailure = error => {
   return {
      type: ADD_EXPENSE_FAILURE,
      payload: {
         error
      }
   }
}
export const deleteExpenseStarted = () => {
   return {
      type: DELETE_EXPENSE_STARTED
   }
}
export const deleteExpenseSuccess = data => {
   return {
      type: DELETE_EXPENSE_SUCCESS,
      payload: {
         data
      }
   }
}
export const deleteExpenseFailure = error => {
   return {
      type: DELETE_EXPENSE_FAILURE,
      payload: {
         error
      }
   }
}

Here, we created one action creator for every possible outcome (success, failure and error) of fetch api. Since we are going to use three web api calls and each call will have three possible outcomes, we use 9 action creators.

Next, create a file, expenseActions.js under actions folder and create three functions to fetch, add and delete expenses and to dispatch state changes.

import {
   getExpenseListStarted, getExpenseListSuccess, getExpenseListFailure,
   addExpenseStarted, addExpenseSuccess, addExpenseFailure,
   deleteExpenseStarted, deleteExpenseSuccess, deleteExpenseFailure
} from "./index";
export const getExpenseList = () => async dispatch => {
   dispatch(getExpenseListStarted());
   try {
      const res = await fetch('http://localhost:8000/api/expenses');
      const data = await res.json();
      var items = [];
      data.forEach((item) => {
         let newItem = {
            id: item._id,
            name: item.name,
            amount: item.amount,
            spendDate: item.spend_date,
            category: item.category
         }
         items.push(newItem)
      });
      dispatch(getExpenseListSuccess(items));
   } catch (err) {
      dispatch(getExpenseListFailure(err.message));
   }
}
export const addExpense = (data) => async dispatch => {
   dispatch(addExpenseStarted());

   let newItem = {
      name: data.name,
      amount: data.amount,
      spend_date: data.spendDate,
      category: data.category
   }
   console.log(newItem);
   try {
      const res = await fetch('http://localhost:8000/api/expense', {
         method: 'POST',
         body: JSON.stringify(newItem),
         headers: {
            "Content-type": "application/json; charset=UTF-8"
         } 
      });
      const data = await res.json();
      newItem.id = data._id;
      dispatch(addExpenseSuccess(newItem));
   } catch (err) {
      console.log(err);
      dispatch(addExpenseFailure(err.message));
   }
}
export const deleteExpense = (id) => async dispatch => {
   dispatch(deleteExpenseStarted());
   try {
      const res = await fetch('http://localhost:8000/api/expense/' + id, {
         method: 'DELETE'
      });
      const data = await res.json();
      dispatch(deleteExpenseSuccess(id));
   } catch (err) {
      dispatch(deleteExpenseFailure(err.message));
   }
}

Here,

  • Used async fetch api to do web api calls.

  • Used dispatch function to dispatch proper action during success, failure and error events.

Create a folder, reducers under src folder and create a file, index.js under reducers folder to create Redux reducers.

import {
   LIST_EXPENSE_STARTED, LIST_EXPENSE_SUCCESS, LIST_EXPENSE_FAILURE,
   ADD_EXPENSE_STARTED, ADD_EXPENSE_SUCCESS, ADD_EXPENSE_FAILURE,
   DELETE_EXPENSE_STARTED, DELETE_EXPENSE_SUCCESS, DELETE_EXPENSE_FAILURE
} from "../actions/types";

// define initial state of user
const initialState = {
   data: null,
   loading: false,
   error: null
}
export default function expenseReducer(state = initialState, action) {
   switch (action.type) {
      case LIST_EXPENSE_STARTED:
         return {
            ...state,
            loading: true
         }
      case LIST_EXPENSE_SUCCESS:
         const { data } = action.payload;
         return {
            ...state,
            data,
            loading: false
         }
      case LIST_EXPENSE_FAILURE:
         const { error } = action.payload;
         return {
            ...state,
            error
         }
      case ADD_EXPENSE_STARTED:
         return {
            ...state,
            loading: true
         }
      case ADD_EXPENSE_SUCCESS:
         return {
            ...state,
            loading: false
         }
      case ADD_EXPENSE_FAILURE:
         const { expenseError } = action.payload;
         return {
            ...state,
            expenseError
         }
      case DELETE_EXPENSE_STARTED:
         return {
            ...state,
            loading: true
         }
      case DELETE_EXPENSE_SUCCESS:
         return {
            ...state,
            data: state.data.filter(expense => expense.id !== action.payload.data),
            loading: false
         }
      case DELETE_EXPENSE_FAILURE:
         const { deleteError } = action.payload;
         return {
            ...state,
            deleteError
         }
      default:
         return state
   }
}

Here, we have updated the redux store state for each action type.

Next, open index.js file under src folder and include Provider component so that all the components can connect and work with the redux store.

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

const store = createStore(rootReducer, applyMiddleware(thunk));

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

Here,

  • Imported createStore and applyMiddleware
  • Imported thunk from redux-thunk library (for async fetch api)
  • Imported Provider from redux library
  • Created newstore using createStore by configuring reducer and thunk middleware
  • Attached the Provider component as top level component with redux store
reactjs_example.htm
Advertisements