177 lines
5.1 KiB
TypeScript
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);
|
|
}
|