Note: If you using version 1.0.89 or below please refer this documentation
React Boilerplate Redux Saga HOC
Installation
This package requires React 16.8.4 or later.
Use the package manager npm to install react-boilerplate-redux-saga-hoc.
npm i react-boilerplate-redux-saga-hoc
or
yarn add react-boilerplate-redux-saga-hoc
# Basic usage
/** App.js **/import React from "react";import PropTypes from "prop-types";import { Provider } from "react-redux";import { compose } from "redux";import {HOC,commonConstants,store as configureStore,} from "react-boilerplate-redux-saga-hoc";import useCustomHook from "./hooks/customHook";const initialState = {};const isWeb = true; // default trueconst store = configureStore(initialState, isWeb);const AuthenticationHOC = HOC({initialState: {profile: {},},apiEndPoints: {TEST_API: {},REGISTER_API: {url: `users/user-signup/`,method: "POST",},},name: "Auth",});const DashboardHOC = HOC({initialState: {profile: {},},apiEndPoints: {TEST_SUB_API: {},DASHBOARD_API: {url: `users/user-signup/`,method: "POST",},},name: "Dash",});const CustomComponent = compose(AuthenticationHOC,DashboardHOC)((props) => {const hooks = useCustomHook(props, { commonConstants });const {test: { data: testData },test_sub: { data: testSubData },} = hooks;console.log(props, hooks);return <div>customHooks</div>;});export default function App(props) {return (<Provider store={store}><CustomComponent /></Provider>);}export default App;
# Store Configuration
Note: No need to configure store seperately.Store can be imported from react-boilerplate-redux-saga-hoc
import React from "react";import { Provider } from "react-redux";import { store as configureStore } from "react-boilerplate-redux-saga-hoc";const initialState = {};const isWeb = true;const store = configureStore(initialState, isWeb); // by default second parameter will be trueexport default function App(props) {return (<Provider store={store}><CustomComponent /></Provider>);}export default App;
Before Proceeding Further
We already knows redux is a valuable tool for organising your state and also redux-saga is a powerful middleware for handling side Effects.With the help of those two tools we have created a package for handling api calls and storing data in an organised way.
# Why should i use this package
Note: This package is not an alternative for redux and redux-saga
This package is mostly for developer who wants to make development faster and also to handle most of the api calls.
# Benefits of using this package
- Handles api calls automatically by itself
- No need to create store, constants, actions, saga, reducer
- It handles cancelling api call by itself
- Handles error, success, cancel, loading, infinite data handling
- No worry about api calls, loaders...etc
- No separate coding needed for react and react native
# Whether this package will support for react-native
Yes ,This package will support for both react and react-native
How does it works
# Step by step process
Importing a package
import {HOC,commonConstants,store,} from "react-boilerplate-redux-saga-hoc";Creating Api-End-Points
import {HOC,commonConstants,store,} from "react-boilerplate-redux-saga-hoc";const BASE_URL = "https://jsonplaceholder.typicode.com";const DEMO_GET_API = {url: `${BASE_URL}/posts`,method: "GET",responseStatusCode: [900],responseStatusCodeKey: "code",responseDataKey: "data",responseMessageKey: "status",errorMessageKey: "error",};const DEMO_POST_API = {url: ({ id }) => `${BASE_URL}/posts/${id}`,method: "POST",responseStatusCode: [900],responseStatusCodeKey: "code",responseDataKey: "data",responseMessageKey: "status",errorMessageKey: "error",};const DEMO_DELETE_API = {url: ({ id }) => `${BASE_URL}/posts/${id}`,method: "DELETE",responseStatusCode: [900],responseStatusCodeKey: "code",responseDataKey: "data",responseMessageKey: "status",errorMessageKey: "error",};const DEMO_PUT_API = {url: ({ id, type }) => `${BASE_URL}/posts/${id}/${type}`,method: "PUT",responseStatusCode: [900],responseStatusCodeKey: "code",responseDataKey: "data",responseMessageKey: "status",errorMessageKey: "error",};Note: You can create a separate file for api-end-points.js.
Creating HOC
import {HOC,commonConstants,store,} from "react-boilerplate-redux-saga-hoc";import {DEMO_GET_API,DEMO_POST_API,DEMO_DELETE_API,DEMO_PUT_API,} from "./api-end-points.js";const Auth_HOC = HOC({initialState: {profile: {},},apiEndPoints: {DEMO_GET_API,DEMO_POST_API,DEMO_DELETE_API,DEMO_PUT_API,},name: "Auth",});Note: When you create HOC for every endpoints it will create constants, actions, reducer, saga for you..
Great: We are almost done.next step is connect hoc with our component.
Connecting with Component
import {HOC,commonConstants,store,} from "react-boilerplate-redux-saga-hoc";import { compose } from "redux";import {DEMO_GET_API,DEMO_POST_API,DEMO_DELETE_API,DEMO_PUT_API,} from "./api-end-points.js";const AuthenticationHOC = HOC({initialState: {profile: {},},apiEndPoints: {DEMO_GET_API,DEMO_POST_API,DEMO_DELETE_API,DEMO_PUT_API,},name: "Auth",});const CustomComponent = compose(AuthenticationHOC)((props) => {const hooks = useCustomHook(props, { commonConstants }); // discuss later belowconst {get: { data },} = hooks;console.log(props, data);return <div>customHooks</div>;});Configuring Store
import {HOC,commonConstants,store as configureStore,} from "react-boilerplate-redux-saga-hoc";const initialState = {};const isWeb = true;const store = configureStore(initialState, isWeb); // by default second parameter will be trueexport default function App(props) {return (<Provider store={store}><CustomComponent /></Provider>);}export default App;Create custom hook
import { useEffect, useMemo } from 'react';const initialLoaderState = true;export default ({Auth_hoc: {actions: {// For API CALLSDEMO_GET_API_CALL,DEMO_POST_API_CALL,DEMO_DELETE_API_CALL,DEMO_PUT_API_CALL,// For API CANCELDEMO_GET_API_CANCEL,DEMO_POST_API_CANCEL,DEMO_DELETE_API_CANCEL,DEMO_PUT_API_CANCEL,// For Modifying reducer without api callsDEMO_GET_API_CUSTOM_TASK,DEMO_POST_API_CUSTOM_TASK,DEMO_DELETE_API_CUSTOM_TASK,DEMO_PUT_API_CUSTOM_TASK}},Auth_data: {// For getting dataDEMO_GET_API,DEMO_POST_API,DEMO_DELETE_API,DEMO_PUT_API,},getData,dispatch,},{ commonConstants: { ON_UNMOUNT } },) => {const { ON_SUCCESS } = commonConstants;useEffect(() => {DEMO_GET_API_CALL({query: {skip: 0,limit: 1,},});DEMO_POST_API_CALL({payload: {name: 'name',age: 20,},params: {id: 1,},});DEMO_DELETE_API_CALL({params: {id: 1,},});DEMO_PUT_API_CALL({params: {id: 1,type: 'male',},});return () => {// For cancelling incomplete api call if you are unmounting this will avoid unwanted network traffic.This is optional no need to pass all the timesDEMO_GET_API_CUSTOM_TASK(ON_UNMOUNT);DEMO_POST_API_CUSTOM_TASK(ON_UNMOUNT);DEMO_DELETE_API_CUSTOM_TASK(ON_UNMOUNT);DEMO_PUT_API_CUSTOM_TASK(ON_UNMOUNT);};}, []);DEMO_GET_API,DEMO_POST_API,DEMO_DELETE_API,DEMO_PUT_API,const get = useMemo(() => getData(DEMO_GET_API, {}, initialLoaderState), [DEMO_GET_API]);const post = useMemo(() => getData(DEMO_POST_API, {}, false), [DEMO_POST_API,]);const put = useMemo(() => getData(DEMO_POST_API, {}, false), [DEMO_PUT_API,]);const delete = useMemo(() => getData(DEMO_DELETE_API, {}, false), [DEMO_DELETE_API,]);// const { loader , data , lastUpdated } = get;return {get,post,put,delete};};Using the hook
import {HOC,commonConstants,store,} from "react-boilerplate-redux-saga-hoc";const CustomComponent = compose(AuthenticationHOC)((props) => {const hooks = useCustomHook(props, { commonConstants });const {get: { data },} = hooks;console.log(props, data);return <div>customHooks</div>;});
Note: This is the basic setup for handling api calls.You will find the more useful features in below such as updating, deleting data..Handling multiple tasks..etc
Storing Data
# storing data automatically by calling api
Note: Data will be stored automatically in the reducer and also it handles all the states..such as error,infinte,loader..etc
const BASE_URL = "https://example.com/";const DEMO_API_CONFIGURATION = {url: `${BASE_URL}user/`,method: "GET",responseStatusCode: [900],responseStatusCodeKey: "code",responseDataKey: "data",responseMessageKey: "status",errorMessageKey: "error",};DEMO_API_CONFIGURATION_CALL();
# sending query parameters to the api
Note: No need to worry about appending query..Its more simpler just pass the object in the query parameter that will append query in the url.And also passing params is simpler..
Note: If you need to pass params in the url..then you have to change the url to function to receive params..just like give below..
const BASE_URL = "https://example.com/";const DEMO_API_CONFIGURATION = {url: ({ id }) => `${BASE_URL}user/${id}`,method: "GET",responseStatusCode: [900],responseStatusCodeKey: "code",responseDataKey: "data",responseMessageKey: "status",errorMessageKey: "error",};DEMO_API_CONFIGURATION_CALL({params: { id: 1 },query: { skip: 10, age: [20, 20] },payload: { age: 20 }, // for post callsparamsSerializer: { arrayFormat: "bracket" }, //default: none - refer query-string npm package});
# Callbacks for handling success or failure
Note: Callback are another helper function which handles errors, success, cancel..etc
const BASE_URL = 'https://example.com/'const DEMO_API_CONFIGURATION = {url: `${BASE_URL}user/${id}`,method: 'GET',responseStatusCode: [900],responseStatusCodeKey: 'code',responseDataKey: 'data',responseMessageKey: 'status',errorMessageKey: 'error',};const responseErrorParser = data =>(Array.isArray(data) &&data.reduce((acc, curr) => {const [key, message] = Object.entries(curr)[0];const payloadKey = key.split(',')[1];return {...acc,[payloadKey]: message,};}, {})) ||{};DEMO_API_CONFIGURATION_CALL({params: { id: 1 },query: { skip: 10,age: [20,20] },paramsSerializer: {arrayFormat: 'bracket'} //default: none - refer query-string npm packagesuccessCallback: ({ res, data, message, status }) => {// handle toast or call any other api},errorCallback: ({ error, errorData: responseErrorParser, message, status, errors }) => {// handle toast or call any other api}});
# Manually storing data by calling custom task
Note: Don't worry about terms Manually,Automatically , its just the common word we used in the real world..The way how it stores or handles data i specified as manually, automatically thats it..You will get used to it.
Automatically - It will handle api calls and stores data and also handles all the errors, success, loaders...etc
Manually - It will slight different instead of api calls we are manually storing or updating data..This util function will help you update data in particular reducer..
import { HOC, commonConstants, store } from "react-boilerplate-redux-saga-hoc";const { ON_SUCCESS } = commonConstants;const { DEMO_API_CONFIGURATION_CUSTOM_TASK } = props;DEMO_API_CONFIGURATION_CUSTOM_TASK(ON_SUCCESS,{},{data: {name: "example",},});
# Getting Data from store
Note: We are almost done with basic setup, api calls, storing data...etcNow the main things we have to retrieve the data from the reducer..Don't Worry that is very much simpler than other task.
getData: Its an util function which gets the data from the particular reducer and also formats the data in order..(DEMO_API_CONFIGURATION, default || typeof data, initial-loader-state, filter)
const { getData, DEMO_API_CONFIGURATION } = props;const test = useMemo(() => getData(DEMO_API_CONFIGURATION, {}, false), [DEMO_API_CONFIGURATION,]);/**test returns{loader,data,latUpdated,isInfinite,isInfiniteEnd}**/
Updating Data
# Updating data automatically by calling api
Note: It will only update if the reponse data is object otherwise it will replace the data.It wont update Array.Array will be handle diiferent way.you will find Below....
const BASE_URL = "https://example.com/";const DEMO_API_CONFIGURATION = {url: `${BASE_URL}user/`,method: "GET",responseStatusCode: [900],responseStatusCodeKey: "code",responseDataKey: "data",responseMessageKey: "status",errorMessageKey: "error",};DEMO_API_CONFIGURATION_CALL({isUpdate: true,});Example: storedData = {name: "example",date: "22-8-2222",};responseData = {name: "example 2",};UpdateData = {name: "example 2",date: "22-8-2222",};
# Updating Data in an Array
Note: If the update data is Array it requires extra 2 parameters to itentify the Object thts is id,key
id - Array || string || number - Array of ids
key - Key example: 'user_id' <String>
const BASE_URL = 'https://example.com/'const DEMO_API_CONFIGURATION = {url: `${BASE_URL}user/${id}`,method: 'GET',responseStatusCode: [900],responseStatusCodeKey: 'code',responseDataKey: 'data',responseMessageKey: 'status',errorMessageKey: 'error',};DEMO_API_CONFIGURATION_CALL({params: { id: 1 },query: { skip: 10,age: [20,20] },payload: { age: 20 }, // for post callsparamsSerializer: {arrayFormat: 'bracket'} //default: none - refer query-string npm packageisUpdate: true,key: 'id',id: [ 2,3 ]});Example:storedData = [{id: 1name: 'example',date: '22-8-2222'},{id: 2name: 'example',date: '22-8-2222'}]responseData = {name: 'example 2'}UpdateData = [{id: 1name: 'example',date: '22-8-2222'},{id: 2name: 'example 2',date: '22-8-2222'}]
Note: In the above scenario id number 3 will be ignored.If u want to append or prepend data there are some other ways...you will find below
# Callbacks for handling Updated Data
Note: There are several callback are available can be used in different requirements
const BASE_URL = 'https://example.com/'const DEMO_API_CONFIGURATION = {url: `${BASE_URL}user/${id}`,method: 'GET',responseStatusCode: [900],responseStatusCodeKey: 'code',responseDataKey: 'data',responseMessageKey: 'status',errorMessageKey: 'error',};DEMO_API_CONFIGURATION_CALL({params: { id: 1 },query: { skip: 10,age: [20,21] },paramsSerializer: {arrayFormat: 'bracket'} //default: none - refer query-string npm packageupdateCallback: (storeData: oldData,responseData: newData) => {return {...oldData,...newData} || oldData.concat(newData) // It will update the data in paricular reducer},updateStateCallback: ({state: updatedState,data: responseData}) => {return updatedState // By default it takes the updated state if returns undefined or null},errorCallback: ({ error, errorData: responseErrorParser, message, status, errors }) => {// handle toast or call any other api}});
# Manually updating data by calling custom task
import { HOC, commonConstants, store } from "react-boilerplate-redux-saga-hoc";const { ON_SUCCESS } = commonConstants;const { DEMO_API_CONFIGURATION_CUSTOM_TASK } = props;DEMO_API_CONFIGURATION_CUSTOM_TASK(ON_SUCCESS,{isUpdate: true,key: "id",id: [1],},{data: {name: "example",},});
# Getting Updated Data from store
const { getData, DEMO_API_CONFIGURATION } = props;const test = useMemo(() => getData(DEMO_API_CONFIGURATION, {}, false), [DEMO_API_CONFIGURATION,]);/**test returns{loader,data,latUpdated,isInfinite,isInfiniteEnd}**/
Handling Infinite Data or Infinite Scrolling
# storing infinite data automatically by calling api
Note: Data will be stored automatically in the reducer and also it handles all the states..such as error,infinte,loader..etc
const BASE_URL = "https://example.com/";const DEMO_API_CONFIGURATION = {url: `${BASE_URL}user/`,method: "GET",responseStatusCode: [900],responseStatusCodeKey: "code",responseDataKey: "data",responseMessageKey: "status",errorMessageKey: "error",};DEMO_API_CONFIGURATION_CALL({isInfinite: true,});
Note: It will append data if already data is array...other wise it will replace the new data
# Prepend data instead of appending data
Note: It will store the data at the top instead of last
const BASE_URL = "https://example.com/";const DEMO_API_CONFIGURATION = {url: ({ id }) => `${BASE_URL}user/${id}`,method: "GET",responseStatusCode: [900],responseStatusCodeKey: "code",responseDataKey: "data",responseMessageKey: "status",errorMessageKey: "error",};DEMO_API_CONFIGURATION_CALL({params: { id: 1 },isInfinite: true,query: { skip: 10, age: [20, 20] },payload: { age: 20 }, // for post callsisAppendTop: true,paramsSerializer: { arrayFormat: "bracket" }, //default: none - refer query-string npm package});
# Callbacks for handling success or failure
Note: Callback are another helper function which handles errors, success, cancel..etc
const BASE_URL = 'https://example.com/'const DEMO_API_CONFIGURATION = {url: `${BASE_URL}user/${id}`,method: 'GET',responseStatusCode: [900],responseStatusCodeKey: 'code',responseDataKey: 'data',responseMessageKey: 'status',errorMessageKey: 'error',};const responseErrorParser = data =>(Array.isArray(data) &&data.reduce((acc, curr) => {const [key, message] = Object.entries(curr)[0];const payloadKey = key.split(',')[1];return {...acc,[payloadKey]: message,};}, {})) ||{};DEMO_API_CONFIGURATION_CALL({params: { id: 1 },isInfinite: true,query: { skip: 10,age: [20,20] },paramsSerializer: {arrayFormat: 'bracket'} //default: none - refer query-string npm packagesuccessCallback: ({ res, data, message, status }) => {// handle toast or call any other api},errorCallback: ({ error, errorData: responseErrorParser, message, status, errors }) => {// handle toast or call any other api}});
# Manually storing or updating infinite data by calling custom task
Note: Calling custom task will allow us to manipulate data without calling any api..such as adding count,linit,skip..etc
import { HOC, commonConstants, store } from "react-boilerplate-redux-saga-hoc";const { ON_SUCCESS } = commonConstants;const { DEMO_API_CONFIGURATION_CUSTOM_TASK } = props;DEMO_API_CONFIGURATION_CUSTOM_TASK(ON_SUCCESS,{isInfinite: true,isAppendTop: true, // by default it will append at the bottom},{data: [{name: "example",},],});
# Getting Data from store
Note: We are almost done with basic setup, api calls, storing data...etcNow the main things we have to retrieve the data from the reducer..Don't Worry that is very much simpler than other task.
getData: Its an util function which gets the data from the particular reducer and also formats the data in order..(DEMO_API_CONFIGURATION, default || typeof data, initial-loader-state, filter)
const { getData, DEMO_API_CONFIGURATION } = props;const test = useMemo(() => getData(DEMO_API_CONFIGURATION, [], false), [DEMO_API_CONFIGURATION,]);/**test returns{loader,data,latUpdated,isInfinite,isInfiniteEnd}**/
Deleting Data
# Deleting data by calling api
Note: Data will be deleted automatically in the reducer and also it handles all the states..such as error,infinte,loader..etc
const BASE_URL = 'https://example.com/';const DEMO_API_CONFIGURATION = {url: ({ id }) => `${BASE_URL}user/${id}`,method: 'GET',responseStatusCode: [900],responseStatusCodeKey: 'code',responseDataKey: 'data',responseMessageKey: 'status',errorMessageKey: 'error',};DEMO_API_CONFIGURATION_CALL({params: { id: 2 },isDelete: true,key: 'id'.id: [ 2 ]});
Note: It will remove that particular object in an array
# Deleting key in an object
Note: It is almost similar to update, but instead of updating particular key it will delete the particular key from the object
const BASE_URL = 'https://example.com/';const DEMO_API_CONFIGURATION = {url: ({ id }) => `${BASE_URL}user/${id}`,method: 'GET',responseStatusCode: [900],responseStatusCodeKey: 'code',responseDataKey: 'data',responseMessageKey: 'status',errorMessageKey: 'error',};DEMO_API_CONFIGURATION_CALL({params: { id: 1 },isInfinite: true,query: { skip: 10, age: [20, 20] },payload: { age: 20 }, // for post callsisAppendTop: true,paramsSerializer: { arrayFormat: 'bracket' }, //default: none - refer query-string npm packageisDeleteKey: true,id: [ 1,2,3 ],key: 'id',deleteKey: [ 'name','age']});Example:data = [ { id: 1, name: 'name1',age: '13',gender: 'male' },{ id: 1, name: 'name1',age: '12',gender: 'female' }]After executing task:data = [ { id: 1, gender: 'male' },{ id: 2, gender: 'female' }]
# Manually Deleting by calling custom task
Note: Calling custom task will allow us to manipulate data without calling any api..such as adding count,linit,skip..etc
import { HOC, commonConstants, store } from "react-boilerplate-redux-saga-hoc";const { ON_SUCCESS } = commonConstants;const { DEMO_API_CONFIGURATION_CUSTOM_TASK } = props;DEMO_API_CONFIGURATION_CUSTOM_TASK(ON_SUCCESS, {isDelete: true,id: [1, 2],key: "id",});
# Getting Data from store
getData: Its an util function which gets the data from the particular reducer and also formats the data in order..(DEMO_API_CONFIGURATION, default || typeof data, initial-loader-state, filter)
const { getData, DEMO_API_CONFIGURATION } = props;const test = useMemo(() => getData(DEMO_API_CONFIGURATION, [], false), [DEMO_API_CONFIGURATION,]);/**test returns{loader,data,latUpdated,isInfinite,isInfiniteEnd}**/
Adding Filters
# Adding filters in api calls
Note: Suppose there is a scenario where same api is used with multiple filters.In that case we have to Store different data.
const BASE_URL = 'https://example.com/';const DEMO_API_CONFIGURATION = {url: ({ id }) => `${BASE_URL}user/${id}`,method: 'GET',responseStatusCode: [900],responseStatusCodeKey: 'code',responseDataKey: 'data',responseMessageKey: 'status',errorMessageKey: 'error',};DEMO_API_CONFIGURATION_CALL({params: { id: 2 },query: { type: 'name' }filter: [ 'name' ],});Example:responseData = { id: 1 }storeData = { name: { id: 1 }
Note: In this case it will store the data in an object with key 'name'
# Deleting key in an Filter object
Note: It is almost similar to update, but instead of updating particular key it will delete the particular key from the object
const BASE_URL = 'https://example.com/';const DEMO_API_CONFIGURATION = {url: ({ id }) => `${BASE_URL}user/${id}`,method: 'GET',responseStatusCode: [900],responseStatusCodeKey: 'code',responseDataKey: 'data',responseMessageKey: 'status',errorMessageKey: 'error',};DEMO_API_CONFIGURATION_CALL({params: { id: 1 },isInfinite: true,query: { skip: 10, age: [20, 20] },payload: { age: 20 }, // for post callsisAppendTop: true,paramsSerializer: { arrayFormat: 'bracket' }, //default: none - refer query-string npm packageisDeleteKey: true,id: [ 1,2,3 ],key: 'id',deleteKey: [ 'name','age'],filter: [ 'name' ],});Example:data = { name : [{ id: 1, name: 'name1',age: '13',gender: 'male' },{ id: 1, name: 'name1',age: '12',gender: 'female' }]}After executing task:data = { name : [{ id: 1, gender: 'male' },{ id: 2, gender: 'female' }}]
# Manually Deleting Filter Data by calling custom task
Note: Calling custom task will allow us to manipulate data without calling any api..such as adding count,limit,skip..etc
import { HOC, commonConstants, store } from "react-boilerplate-redux-saga-hoc";const { ON_SUCCESS } = commonConstants;const { DEMO_API_CONFIGURATION_CUSTOM_TASK } = props;DEMO_API_CONFIGURATION_CUSTOM_TASK(ON_SUCCESS, {isDelete: true,id: [1, 2],key: "id",filter: ["name"],});
# Getting Filter Data from store
getData: Its an util function which gets the data from the particular reducer and also formats the data in order..(DEMO_API_CONFIGURATION, default || typeof data, initial-loader-state, filter)
const { getData, DEMO_API_CONFIGURATION } = props;const nameData = useMemo(() => getData(DEMO_API_CONFIGURATION, [], false, ["name"]),[DEMO_API_CONFIGURATION]);const ageData = useMemo(() => getData(DEMO_API_CONFIGURATION, [], false, ["age"]),[DEMO_API_CONFIGURATION]);/**test returns{loader,data,latUpdated,isInfinite,isInfiniteEnd}**/
Using Subkey for accessing deep object
# Adding filters in Deep Object
Note: Incase you want to update deep object we can use subkey to navigate to that object
const BASE_URL = 'https://example.com/';const DEMO_API_CONFIGURATION = {url: ({ id }) => `${BASE_URL}user/${id}`,method: 'GET',responseStatusCode: [900],responseStatusCodeKey: 'code',responseDataKey: 'data',responseMessageKey: 'status',errorMessageKey: 'error',};DEMO_API_CONFIGURATION_CALL({subKey: [ 'data' ],params: { id: 2 },query: { type: 'name' }filter: [ 'name' ],});Example:responseData = { data : { id: 1 } }storeData = { name: { data: { id: 1 } }
Note: In this case it will store the data in an object with key 'name'
# Deleting key in an deep object
Note: It is almost similar to update, but instead of updating particular key it will delete the particular key from the object
const BASE_URL = 'https://example.com/';const DEMO_API_CONFIGURATION = {url: ({ id }) => `${BASE_URL}user/${id}`,method: 'GET',responseStatusCode: [900],responseStatusCodeKey: 'code',responseDataKey: 'data',responseMessageKey: 'status',errorMessageKey: 'error',};DEMO_API_CONFIGURATION_CALL({params: { id: 1 },subKey: [ 'data' ],isInfinite: true,query: { skip: 10, age: [20, 20] },payload: { age: 20 }, // for post callsisAppendTop: true,paramsSerializer: { arrayFormat: 'bracket' }, //default: none - refer query-string npm packageisDeleteKey: true,id: [ 1,2,3 ],key: 'id',deleteKey: [ 'name','age'],filter: [ 'name' ],});Example:data = { name : [{ id: 1, name: 'name1',age: '13',gender: 'male' },{ id: 1, name: 'name1',age: '12',gender: 'female' }]}After executing task:data = { name : { data : [{ id: 1, gender: 'male' },{ id: 2, gender: 'female' }]}}
# Manually Deleting Subkey Data by calling custom task
Note: Calling custom task will allow us to manipulate data without calling any api..such as adding count,limit,skip..etc
import { HOC, commonConstants, store } from "react-boilerplate-redux-saga-hoc";const { ON_SUCCESS } = commonConstants;const { DEMO_API_CONFIGURATION_CUSTOM_TASK } = props;DEMO_API_CONFIGURATION_CUSTOM_TASK(ON_SUCCESS, {isDelete: true,subKey: ["data"],id: [1, 2],key: "id",filter: ["name"],});
# Getting Filter Data from store
getData: Its an util function which gets the data from the particular reducer and also formats the data in order..(DEMO_API_CONFIGURATION, default || typeof data, initial-loader-state, filter)
const { getData, DEMO_API_CONFIGURATION } = props;const nameData = useMemo(() => getData(DEMO_API_CONFIGURATION, [], false, ["name"]),[DEMO_API_CONFIGURATION]);/**test returns{loader,data,latUpdated,isInfinite,isInfiniteEnd}**/
Advanced Topics
# Creating Custom Reducer
import { HOC, commonConstants, store } from "react-boilerplate-redux-saga-hoc";const AuthenticationHOC = HOC({initialState: {profile: {},},apiEndPoints: {TEST_API: {},REGISTER_API: {url: `users/user-signup/`,method: "POST",},},constantReducer: ({ type, state, action, constants, initialState }) => {if (type === "LOGOUT") return initialState;return state;},name: "Auth",});Example: const { dispatch } = props;dispatch({ type: "LOGOUT" });
# Modifying reducer constants
import { HOC, commonConstants, store } from "react-boilerplate-redux-saga-hoc";const { CALL, ON_SUCCESS } = commonConstants;const reducer = ({constants,successData,restSuccessData,payload,query,params,restPayload,loadingStatus,statusCode,type,method,statusMessage,errorData,restErrorData,ResetState,InitialState,commonHandler,defaultReducerHandler,}) => {switch (type) {case "RESET":switch (method) {case ON_SUCCESS:return isMobileApp? newObject(InitialState): newObject(state, ResetState);default:return state;}case authenticationConstants.VERIFY_OTP_API[CALL]:switch (method) {case ON_SUCCESS:return newState(({ [type]: Data }) => ({profile: successData,isLoggedIn: !!successData.data.mobile_number,[type]: newObject(Data, {lastUpdated: generateTimeStamp(),data: successData,}),}));default:return state;}default:return defaultReducerHandler();}};const AuthenticationHOC = HOC({initialState: {profile: {},},apiEndPoints: {TEST_API: {},VERIFY_OTP_API: {url: `users/verify-otp/`,method: "POST",},REGISTER_API: {url: `users/user-signup/`,method: "POST",},},reducer,constantReducer: ({ type, state, action, constants, initialState }) => {if (type === "LOGOUT") return initialState;return state;},name: "Auth",});
# Don't reset on setting to initial state
import { HOC, commonConstants, store } from 'react-boilerplate-redux-saga-hoc';const TEST_API = {};const REGISTER_API = {url: `users/user-signup/`,method: 'POST',}const AuthenticationHOC = HOC({initialState: {profile: {},},dontReset: {TEST_API,REGISTER_API}apiEndPoints: {TEST_API,REGISTER_API,},constantReducer: ({ type, state, action, constants, initialState }) => {if (type === 'LOGOUT')return initialState;return state;},name: 'Auth',});Example:const { dispatch } = props;dispatch({ type: 'RESET' });
Cancelling Api Calls
# cancel on unmount
Note: This function will help cancel unwanted api calls
const { ON_SUCCESS, ON_UNMOUNT } = commonConstants;const { DEMO_API_CONFIGURATION_CANCEL, DEMO_API_CONFIGURATION_CALL } = props;useEffect(() => {DEMO_API_CONFIGURATION_CALL();return () => {DEMO_API_CONFIGURATION_CANCEL(ON_UNMOUNT);};}, []);
Note: ON_UMOUNT will reset the particular state to initial
# Axios Interceptors
import { HOC, commonConstants, store } from 'react-boilerplate-redux-saga-hoc';mport axios from 'axios';import promise from 'promise';const request = axios;request.defaults.withCredentials = true;request.interceptors.request.use(config => {if (!config.headers.Authorization) {const token = localStorage.getItem('token');if (token)request.defaults.headers.common.Authorization = `Bearer ${token}`;}return config;},error => promise.reject(error),);request.interceptors.response.use(response => {return response;},error => Promise.reject(error),);const AuthenticationHOC = HOC({initialState: {profile: {},},apiEndPoints: {TEST_API: {},REGISTER_API: {url: `users/user-signup/`,method: 'POST',},},axiosInterceptors: request,constantReducer: ({ type, state, action, constants, initialState }) => {if (type === 'LOGOUT')return initialState;return state;},name: 'Auth',});Example:const { dispatch } = props;dispatch({ type: 'LOGOUT' });
# Inject saga and reducer to the store
Note: By injecting reducer and saga you can able to create your own reducer and saga.It helps in most of the scnarios such as polling,creating youer own logic ..etc
import { takeLatest } from 'redux-saga/effects';import { injectReducer,injectSaga } from 'react-boilerplate-redux-saga-hoc';const reducer = (state,action) => {return state;}function* getRepos() {}const saga = function* () {yield takeLatest('LOAD_DATA', getRepos);}const withSaga = injectSaga({'dashboard',saga})const withReducer = injectReducer({'dashboard',reducer})export default compose(withSaga,withReducer,)(Dashboard);
# Inject saga and reducer to the store by using hooks
Note: By injecting reducer and saga you can able to create your own reducer and saga. It helps in most of the scenarios such as polling,creating your own logic..etc
import { takeLatest } from "redux-saga/effects";import {useInjectReducer,useInjectSaga,} from "react-boilerplate-redux-saga-hoc";const reducer = (state, action) => {return state;};function* getRepos() {}const saga = function*() {yield takeLatest("LOAD_DATA", getRepos);};const key = "Dashboard";const Dashboard = () => {useInjectReducer({ key, reducer });useInjectSaga({ key, saga });return <div />;};export default Dashboard;
Util Functions
# Safe Function
Note: This function will be used for accessing deep level of object.
Example 1:const defaultValue = '';const object = { name: { name: { age: { person: 'jfjj' } } } }const Person = Safe(object , '.name.name.age.person', defaultValue);Example 2:const callback = (person) => return person + person;const Person = Safe(object , '.name.name.age.person', defaultValue , callback)
Note: defaultValue is typeof value should return ,if the value return array by default safe function will return default value...if don't pass any value in third parameter it return the ececuted value otherwise return null.
# Other Util functions
import { cloneObject , newObject } from 'react-boilerplate-redux-saga-hoc';Example 1:const oldObject = { name: '' };const newObject = { age: '' };const obj = cloneObject(oldObject, newObject)obj returns { name: '', age: '' }Example 2:const object = { name: '',profile: { name : '' } };const obj = newObject(object,({ profile }) => ({profile: newObject(profile, { age: 20 })}))obj returns { name: '', age: 20 }
# PARAMS
isUpdate - update an object or array <Boolean>isUpdateKey -<Boolean> updating or adding a key in existing objectupdateIdCallback - <Function>updateStateCallback - <Function>subKey - <Array> accessing deep objectisInfinite - storing infinite data <Boolean>isDelete - deleting a data in an array <Boolean> takes id, key as an extraparameterisDeleteKey - delete a key in an object <Boolean>deleteKey - <Array> of keys to deletefilter - <Array> storing data based on filterfilter - <Array> update n number of filters at single taskeg: [['filter1'],['filter2']]id - <Array> || <string> || <number>key - id Key example: 'user_id' <String>clearData - clearing data <Boolean> for infiniteisAppendTop - for storing data in top for infinitelimit - <Number> for infinitequery - <Object> adding query parameters to urlparams - <Object> params for aps eg:api/:idsuccessCallback - <Function> <{res : <Object>,data<Object>,status, message}>errorCallback - <Function> <{error : <Error instance>,errorData<Object>,status, message}>updateCallback - <Function> <Object>payload - <Function>ON_SUCCESS - storing data based on success data or loaded dataON_UNMOUNT - reset the dataON_ERROR - setting the loader false and error dataApi End Point Params -* url,* method,* responseMessageKey,* responseDataKey,* responseStatusCodeKey,* responseStatusCodeKey,* errorStatusKey,* errorMessageKey,* errorDataKey,* errorHandlerStatusCode,* effect - takeLatest default || 'every' for takeEveryGetData - returns* data - <Object>* loader - <Boolean>* lastUpdated - <Boolean>* isInfinite - <Boolean>* isInfiniteEnd - <Boolean>GetData - params* Object(data) - <Object>* defaultValue - <Array || Object || Boolean || null || String >* loader - <Boolean>* filter - <Array>
# Handling Multiple tasks
// hooks/customHook.jsimport { useEffect, useMemo } from 'react';export default ({TEST_API_CUSTOM_TASK,TEST_SUB_API_CUSTOM_TASK,Auth: { TEST_API },Dash: { TEST_SUB_API }getData,},{ commonConstants },) => {const { ON_SUCCESS } = commonConstants;useEffect(() => {TEST_SUB_API_CUSTOM_TASK(ON_SUCCESS,{},{data: {count: 20,items: [{name: 'cartoon',},],},},);TEST_SUB_API_CUSTOM_TASK(ON_SUCCESS,{},{data: {count: 20,items: [{name: 'cartoon',},],},},);setTimeout(() => {TEST_SUB_API_CUSTOM_TASK(ON_SUCCESS,{subKey: ['items'],isInfinite: true,},{data: {count: 50,items: [{name: 'mango',},],},},);}, 1);setTimeout(() => {TEST_SUB_API_CUSTOM_TASK(ON_SUCCESS,{subKey: ['items'],isDelete: true,id: 'cartoon',key: 'name',},{},);}, 2);setTimeout(() => {TEST_SUB_API_CUSTOM_TASK(ON_SUCCESS,{subKey: ['items'],isDeleteKey: true,id: 'mango',key: 'name',deleteKey: ['name'],},{},);}, 3);setTimeout(() => {TEST_SUB_API_CUSTOM_TASK(ON_SUCCESS,{// subKey: ['items'],// isInfinite: true,},{data: {count: 100,items: [{name: '1',},{name: '2',},{name: '3',},{name: '4',},],},},);}, 4);setTimeout(() => {TEST_SUB_API_CUSTOM_TASK(ON_SUCCESS, {tasks: [{task: 'add',params: {clearData: true,isInfinite: true,filter: ['check'],},value: {count: 30,items: [{ name: '10' },{ name: '15' },{ name: '25' },{ name: '35' },{ name: '45' },],},},{task: 'isDelete',params: {subKey: ['items'],id: '1',key: 'name',filter: ['check'],},value: {},},{task: 'append',params: {subKey: ['items'],isInfinite: true,// clearData: true,filter: ['check'],},value: {count: 300,items: [{ name: '54' }],data: [{ name: '54' }],},},{task: 'isDelete',params: {subKey: ['items'],id: ['3', '4', '5'],key: 'name',filter: ['check'],},value: {},},{task: 'isUpdate',params: {subKey: ['items'],id: ['10', '25', '15'],key: 'name',filter: ['check'],values: {'10': {status: 'married',},'25': {status: 'unmarried',},'15': {status: 'married',},},},value: {count: 30,limit: 20,// items: { age: 30 },},},{task: 'isDeleteKey',params: {subKey: ['items'],id: ['10'],key: 'name',filter: ['check'],deleteKey: ['name'],},value: {},},{task: 'isToggleKey',params: {subKey: ['items'],id: ['15'],key: 'name',filter: ['check'],toggleKey: ['name', 'status'],},value: {},},],});TEST_API_CUSTOM_TASK(ON_SUCCESS, {tasks: [{task: 'add',params: {clearData: true,isInfinite: true,filter: ['check'],},value: [{ name: '10' },{ name: '15' },{ name: '25' },{ name: '35' },{ name: '45' },],},{task: 'isDelete',params: {// subKey: ['items'],id: '1',key: 'name',filter: ['check'],},value: {},},{task: 'append',params: {// subKey: ['items'],isInfinite: true,filter: ['check'],},value: [{ name: '5' }],},{task: 'append',params: {// subKey: ['items'],isInfinite: true,filter: ['check'],// clearData: true,},value: [{ name: '54' }],},{task: 'isDelete',params: {// subKey: ['items'],id: ['3', '4'],key: 'name',filter: ['check'],},value: {},},{task: 'isUpdate',params: {// subKey: ['items'],id: ['10', '25', '15'],key: 'name',filter: ['check'],values: {'10': {status: 'married',},'25': {status: 'unmarried',},'15': {status: 'married',},},},value: {// items: { age: 30 },},},{task: 'isDeleteKey',params: {// subKey: ['items'],id: ['10'],key: 'name',filter: ['check'],deleteKey: ['status'],},value: {},},{task: 'isToggleKey',params: {id: ['25'],key: 'name',filter: ['check'],toggleKey: ['name', 'status'],},value: {},},],});}, 5);}, []);const test = useMemo(() => getData(TEST_API, {}, false), [TEST_API]);const test_sub = useMemo(() => getData(TEST_SUB_API, {}, false), [TEST_SUB_API,]);return {test,test_sub,};};
Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Please make sure to update tests as appropriate.