dashboard/src/services/api/redux/reducers.ts

177 lines
5.1 KiB
TypeScript

import { StartAction, SuccessAction, FailureAction, ApiStatus, ApiState } from './types';
export const INITIAL_API_STATUS: ApiStatus = {
isEmpty: true,
isLoading: false,
isValid: false,
isError: false,
dateTime: Date.now(),
error: null,
errorMessage: null,
};
export const INITIAL_API_STATE: ApiState = {
_status: INITIAL_API_STATUS,
items: [],
};
type AllActions = StartAction | SuccessAction | FailureAction;
/**
* Use this function to create any reducer which handles api communication
* @param {Array} actionTypes Array of action types associated with fetchThunk e.g. USER_REQUEST,
* USER_SUCCESS and USER_ERROR
* @param {function} dataAdapter function which will be called to transform response body
* to format you want to use in state
* @param {function} errorAdapter function which will be called to transform error
* to format you want to use in state
* @returns {function} reducer handles api requests, saves response body as data and status
*/
export const createApiReducer =
(
actionTypes: string[],
dataAdapter: (data: any, state?: any) => any = (data: any) => data,
errorAdapter = (data: any) => data,
) =>
(state = INITIAL_API_STATE, action: AllActions) => {
const [REQUEST, SUCCESS, ERROR] = actionTypes;
switch (action.type) {
case REQUEST:
return {
...state,
_status: {
...state._status,
isLoading: true,
dateTime: Date.now(),
},
};
case SUCCESS:
return {
_status: {
isEmpty: false,
isLoading: false,
isValid: true,
isError: false,
dateTime: Date.now(),
error: null,
errorMessage: null,
},
...dataAdapter(action.payload, state),
};
case ERROR:
return {
...state,
_status: {
isEmpty: false,
isLoading: false,
isValid: false,
isError: true,
error: action.payload,
errorMessage: errorAdapter(action.payload),
},
};
default:
return state;
}
};
/**
* This is higher order reducer
* Use this if you want your state is combination of results of multiple reducers
* under the same key, e.g. you can get the same data from multiple endpoints
*/
export const chainReducers =
(initialState: any, ...args: any[]) =>
(state = initialState, action: any) =>
args.reduce((newState, reducer) => reducer(newState, action), state);
/**
* Creates an opinionated CRUD reducer
*/
export function createCrudApiReducer<T>(
startActionType: string,
failureActionType: string,
fetchActionType: string,
addActionType: string,
updateActionType: string,
deleteActionType: string,
transformItem: (data: any) => T,
keyField = 'id',
) {
const fetchReducer = createApiReducer(
[startActionType, fetchActionType, failureActionType],
(data) => ({ _meta: data.meta, items: (data.data || data).map(transformItem) }),
(data) => data,
);
const addReducer = createApiReducer(
[startActionType, addActionType, failureActionType],
(data, state) => ({ items: [...state.items, transformItem(data)] }),
(data) => data,
);
const updateReducer = createApiReducer(
[startActionType, updateActionType, failureActionType],
(data, state) => ({
items: state.items.map((i: any) => (i[keyField] === data[keyField] ? transformItem(data) : i)),
}),
(data) => data,
);
const deleteReducer = createApiReducer(
[startActionType, deleteActionType, failureActionType],
(data, state) => ({
items: state.items.filter((i: any) => i[keyField] !== data[keyField]),
}),
(data) => data,
);
return chainReducers(INITIAL_API_STATE, fetchReducer, addReducer, updateReducer, deleteReducer);
}
/**
* Creates an opinionated CRUD reducer without paging
*/
export function createCrudApiReducerWithoutPaging<T>(
startActionType: string,
failureActionType: string,
fetchActionType: string,
addActionType: string,
updateActionType: string,
deleteActionType: string,
transformItem: (data: any) => T,
keyField = 'id',
) {
const fetchReducer = createApiReducer(
[startActionType, fetchActionType, failureActionType],
(data) => ({ items: data.map(transformItem) }),
(data) => data,
);
const addReducer = createApiReducer(
[startActionType, addActionType, failureActionType],
(data, state) => ({ items: [...state.items, transformItem(data)] }),
(data) => data,
);
const updateReducer = createApiReducer(
[startActionType, updateActionType, failureActionType],
(data, state) => ({
items: state.items.map((i: any) => (i[keyField] === data[keyField] ? transformItem(data) : i)),
}),
(data) => data,
);
const deleteReducer = createApiReducer(
[startActionType, deleteActionType, failureActionType],
(data, state) => ({
items: state.items.filter((i: any) => i[keyField] !== data[keyField]),
}),
(data) => data,
);
return chainReducers(INITIAL_API_STATE, fetchReducer, addReducer, updateReducer, deleteReducer);
}