-
Notifications
You must be signed in to change notification settings - Fork 0
/
model-redux.ts
99 lines (89 loc) · 2.37 KB
/
model-redux.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import startsWith = require('lodash/startsWith')
import { Dispatch } from 'redux'
const actionPrefix = '@react-sync-tools/modelUpdate'
export const storeKey = 'react-sync-tools-models'
/**
* Action creator that allows to either completely replace the model state or
* allows you to pass a function acting as a reducer.
* @param modelId
* @param newStateOrFunc
*/
export const modelSetStateActionCreator = <S extends any>(
modelId: string,
newStateOrFunc: ((state: S) => S) | any,
) => (dispatch: Dispatch, getState: () => S) => {
let payload = newStateOrFunc
if (typeof newStateOrFunc === 'function') {
payload = newStateOrFunc(getState()[storeKey][modelId])
}
dispatch({
type: [actionPrefix, modelId, 'setState'].join('/'),
payload,
})
}
/**
* Action creator used for model actions. Type of the action is a path to reducer
* and payload will be args meant to be passed to that reducer.
* @param modelId
* @param actionName
* @param args
*/
export const modelUpdateActionCreator = <S extends any>(
modelId: string,
actionName: string,
...args: any[]
) => {
return {
type: [actionPrefix, modelId, actionName].join('/'),
payload: args,
}
}
/**
* Single reducer for all models. Actual reducers are dynamically dispatched
* based on the type from reducers object.
* @param state
* @param action
*/
export const reducer = (
state: any = {},
action: { type: string; payload: any } & {},
) => {
if (!startsWith(action.type, actionPrefix)) {
return state
}
const [, , modelId, actionName] = action.type.split('/')
// Base action that is allways provided for each model.
if (actionName === 'setState') {
return {
...state,
[modelId]: action.payload,
}
} else {
return {
...state,
// Invoking the specific reducer
[modelId]: reducers[modelId][actionName](
state[modelId],
...action.payload,
),
}
}
}
type ReducerFunc<S> = (state: S, ...args: any[]) => S
interface ReducersMap {
[modelId: string]: {
[actionName: string]: ReducerFunc<any>
}
}
/**
* Model action functions are registered here and called from the reducer.
*/
const reducers: ReducersMap = {}
export const registerReducer = (
modelId: string,
actionName: string,
func: (state: any, ...args: any[]) => any,
) => {
reducers[modelId] = reducers[modelId] || {}
reducers[modelId][actionName] = func
}