Add CRUDReducer
This commit is contained in:
parent
ed047be2e3
commit
32b527d92f
236
packages/client/src/crud/CRUDReducer.ts
Normal file
236
packages/client/src/crud/CRUDReducer.ts
Normal file
@ -0,0 +1,236 @@
|
||||
import {IAction} from '../actions'
|
||||
import {indexBy, without} from '@rondo/common'
|
||||
|
||||
export type ICRUDMethod =
|
||||
'put' | 'post' | 'delete' | 'get' | 'getMany'
|
||||
|
||||
export interface ICRUDIdable {
|
||||
readonly id: number
|
||||
}
|
||||
|
||||
export interface ICRUDMethodStatus {
|
||||
readonly isLoading: boolean
|
||||
readonly error: string
|
||||
}
|
||||
|
||||
export interface ICRUDState<T extends ICRUDIdable> {
|
||||
readonly ids: ReadonlyArray<number>
|
||||
readonly byId: Record<number, T>
|
||||
status: ICRUDStatus
|
||||
}
|
||||
|
||||
export interface ICRUDStatus {
|
||||
readonly post: ICRUDMethodStatus
|
||||
readonly put: ICRUDMethodStatus
|
||||
readonly delete: ICRUDMethodStatus
|
||||
readonly get: ICRUDMethodStatus
|
||||
readonly getMany: ICRUDMethodStatus
|
||||
}
|
||||
|
||||
export interface ICRUDActions {
|
||||
readonly post: string
|
||||
readonly put: string
|
||||
readonly delete: string
|
||||
readonly get: string
|
||||
readonly getMany: string
|
||||
}
|
||||
|
||||
export interface ICRUDAction<P, T extends string = string> extends IAction<T> {
|
||||
payload: P,
|
||||
}
|
||||
|
||||
export class CRUDReducer<T extends ICRUDIdable> {
|
||||
readonly defaultState: ICRUDState<T>
|
||||
readonly actionTypes: ReturnType<CRUDReducer<T>['getActionTypes']>
|
||||
|
||||
constructor(
|
||||
readonly actions: ICRUDActions,
|
||||
readonly pendingExtension = '_PENDING',
|
||||
readonly resolvedExtension = '_RESOLVED',
|
||||
readonly rejectedExtension = '_REJECTED',
|
||||
) {
|
||||
this.defaultState = {
|
||||
ids: [],
|
||||
byId: {},
|
||||
|
||||
status: {
|
||||
post: {
|
||||
error: '',
|
||||
isLoading: false,
|
||||
},
|
||||
put: {
|
||||
error: '',
|
||||
isLoading: false,
|
||||
},
|
||||
delete: {
|
||||
error: '',
|
||||
isLoading: false,
|
||||
},
|
||||
get: {
|
||||
error: '',
|
||||
isLoading: false,
|
||||
},
|
||||
getMany: {
|
||||
error: '',
|
||||
isLoading: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
this.actionTypes = this.getActionTypes()
|
||||
}
|
||||
|
||||
getPromiseActionNames(type: string) {
|
||||
return {
|
||||
pending: type + this.pendingExtension,
|
||||
resolved: type + this.resolvedExtension,
|
||||
rejected: type + this.rejectedExtension,
|
||||
}
|
||||
}
|
||||
|
||||
getActionTypes() {
|
||||
const {actions} = this
|
||||
return {
|
||||
put: this.getPromiseActionNames(actions.put),
|
||||
post: this.getPromiseActionNames(actions.post),
|
||||
delete: this.getPromiseActionNames(actions.delete),
|
||||
get: this.getPromiseActionNames(actions.get),
|
||||
getMany: this.getPromiseActionNames(actions.getMany),
|
||||
}
|
||||
}
|
||||
|
||||
getUpdatedStatus(
|
||||
state: ICRUDStatus,
|
||||
method: ICRUDMethod,
|
||||
status: ICRUDMethodStatus,
|
||||
): ICRUDStatus {
|
||||
return {
|
||||
...state,
|
||||
[method]: status,
|
||||
}
|
||||
}
|
||||
|
||||
getMethod(actionType: string): ICRUDMethod {
|
||||
const {get, put, post, delete: _delete, getMany} = this.actionTypes
|
||||
switch (actionType) {
|
||||
case get.pending:
|
||||
case get.rejected:
|
||||
return 'get'
|
||||
case put.pending:
|
||||
case put.rejected:
|
||||
return 'put'
|
||||
case post.pending:
|
||||
case post.rejected:
|
||||
return 'post'
|
||||
case _delete.pending:
|
||||
case _delete.rejected:
|
||||
return 'delete'
|
||||
case getMany.pending:
|
||||
case getMany.rejected:
|
||||
return 'getMany'
|
||||
default:
|
||||
throw new Error('Unknown action type: ' + actionType)
|
||||
}
|
||||
}
|
||||
|
||||
getSuccessStatus(): ICRUDMethodStatus {
|
||||
return {
|
||||
isLoading: false,
|
||||
error: '',
|
||||
}
|
||||
}
|
||||
|
||||
reduce = (state: ICRUDState<T>, action: ICRUDAction<T | T[]>)
|
||||
: ICRUDState<T> => {
|
||||
const {defaultState} = this
|
||||
state = state || defaultState
|
||||
|
||||
const {get, put, post, delete: _delete, getMany} = this.actionTypes
|
||||
|
||||
switch (action.type) {
|
||||
case put.pending:
|
||||
case post.pending:
|
||||
case _delete.pending:
|
||||
case getMany.pending:
|
||||
case get.pending:
|
||||
const pendingMethod = this.getMethod(action.type)
|
||||
return {
|
||||
...state,
|
||||
status: this.getUpdatedStatus(state.status, pendingMethod, {
|
||||
isLoading: true,
|
||||
error: '',
|
||||
}),
|
||||
}
|
||||
|
||||
case put.rejected:
|
||||
case post.rejected:
|
||||
case _delete.rejected:
|
||||
case getMany.rejected:
|
||||
case get.rejected:
|
||||
const rejectedMethod = this.getMethod(action.type)
|
||||
const rejectedAction = action as any
|
||||
return {
|
||||
...state,
|
||||
status: this.getUpdatedStatus(state.status, rejectedMethod, {
|
||||
isLoading: false,
|
||||
error: rejectedAction.error
|
||||
? rejectedAction.error.message
|
||||
: 'An error occurred',
|
||||
}),
|
||||
}
|
||||
|
||||
case get.resolved:
|
||||
const getPayload = action.payload as T
|
||||
return {
|
||||
...state,
|
||||
ids: [...state.ids, getPayload.id],
|
||||
byId: {
|
||||
[getPayload.id]: getPayload,
|
||||
},
|
||||
status: this.getUpdatedStatus(
|
||||
state.status, 'get', this.getSuccessStatus()),
|
||||
}
|
||||
case post.resolved:
|
||||
const postPayload = action.payload as T
|
||||
return {
|
||||
...state,
|
||||
ids: [...state.ids, postPayload.id],
|
||||
byId: {
|
||||
[postPayload.id]: postPayload,
|
||||
},
|
||||
status: this.getUpdatedStatus(
|
||||
state.status, 'post', this.getSuccessStatus()),
|
||||
}
|
||||
case put.resolved:
|
||||
const putPayload = action.payload as T
|
||||
return {
|
||||
...state,
|
||||
byId: {
|
||||
[putPayload.id]: putPayload,
|
||||
},
|
||||
status: this.getUpdatedStatus(
|
||||
state.status, 'put', this.getSuccessStatus()),
|
||||
}
|
||||
case _delete.resolved:
|
||||
const deletePayload = action.payload as T
|
||||
return {
|
||||
...state,
|
||||
ids: state.ids.filter(id => id !== deletePayload.id),
|
||||
byId: without(state.byId, deletePayload.id),
|
||||
status: this.getUpdatedStatus(
|
||||
state.status, 'delete', this.getSuccessStatus()),
|
||||
}
|
||||
case getMany.resolved:
|
||||
const getManyPayload = action.payload as T[]
|
||||
return {
|
||||
...state,
|
||||
ids: getManyPayload.map(item => item.id),
|
||||
byId: indexBy(getManyPayload, 'id' as any),
|
||||
status: this.getUpdatedStatus(
|
||||
state.status, 'getMany', this.getSuccessStatus()),
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user