-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathindex.js
168 lines (144 loc) · 4.25 KB
/
index.js
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import { useReducer, useEffect, useRef, useCallback } from "react";
// for testing
export const NO_UPDATE_SYMBOL = Symbol("NO_UPDATE_SYMBOL");
export const Update = state => ({ state });
export const NoUpdate = () => NO_UPDATE_SYMBOL;
export const UpdateWithSideEffect = (state, sideEffects) => ({
state,
sideEffects
});
export const SideEffect = sideEffects => ({ sideEffects });
//for testing
export async function executeSideEffects({ sideEffects, state, dispatch }) {
let cancelFuncs = [];
if (sideEffects) {
while (sideEffects.length) {
const sideEffect = sideEffects.shift();
const cancel = sideEffect(state, dispatch);
if (cancel && typeof cancel === "function") {
cancelFuncs.push(cancel);
}
}
}
return Promise.resolve(cancelFuncs);
}
// for testing
export function mergeState(prevState, newState, isUpdate) {
const existingEffects = isUpdate ? prevState.sideEffects : [];
const newSideEffects = newState.sideEffects
? [
...existingEffects,
...(Array.isArray(newState.sideEffects) ? newState.sideEffects : [newState.sideEffects]),
]
: prevState.sideEffects;
const hasNewState =
typeof newState.hasOwnProperty === 'function' &&
newState.hasOwnProperty('state');
let updatedState;
if (isUpdate) {
updatedState = hasNewState ? newState.state : prevState.state;
} else {
updatedState = newState.state;
}
return {
state: updatedState,
sideEffects: newSideEffects
};
}
function finalReducer(reducer) {
return function(state, action) {
if (action === NO_UPDATE_SYMBOL) {
return state;
}
let newState = reducer(state.state, action);
return mergeState(state, newState, true);
};
}
export default function useCreateReducerWithEffect(
reducer,
initialState,
init
) {
const memoizedReducer = useCallback(finalReducer(reducer), [reducer]);
const [{ state, sideEffects }, dispatch] = useReducer(
memoizedReducer,
{
state: initialState,
sideEffects: []
},
(state) => {
let newState;
if (typeof init === 'function') {
newState = init(state);
}
return typeof newState !== 'undefined' ? mergeState(state, newState, false) : state;
}
);
let cancelFuncs = useRef([]);
useEffect(() => {
if (sideEffects.length) {
async function asyncEffects() {
async function runSideEffects() {
const cancels = await executeSideEffects({
sideEffects,
state,
dispatch
});
return cancels;
}
const cancels = await runSideEffects();
cancelFuncs.current = cancels;
}
asyncEffects();
if (cancelFuncs.current.length) {
cancelFuncs.current.forEach(func => {
func(state);
cancelFuncs.current = [];
});
}
}
}, [sideEffects]); //eslint-disable-line
return [state, dispatch];
}
export function composeReducers(reducers) {
return (state, action) => {
let reducerCount = reducers.length;
let sideEffects = [];
let noUpdateCount = 0;
const reducedResult = reducers.reduceRight((prevState, reducer, index) => {
// This is to handle the asymmetry in the useReducerWithSideFffect API.
// Whereas regular reducers have a consistent API that is state in, state out
// useReducerWithSideEffects has state in, state+sideEffects out
let state;
if (index === reducerCount - 1) {
state = prevState;
} else {
state = prevState.state;
}
const result = reducer(state, action);
let returnValue;
// When we do not have an update, then we increment our no-update counter and
// return the previous state.
if (result === NO_UPDATE_SYMBOL) {
noUpdateCount++;
returnValue = {
state,
};
} else {
returnValue = result;
}
if (result && Array.isArray(result.sideEffects)) {
sideEffects = sideEffects.concat(result.sideEffects);
}
return returnValue;
}, state);
const noUpdateOccurred = noUpdateCount === reducerCount;
if (noUpdateOccurred) {
return NO_UPDATE_SYMBOL;
}
return {
state: reducedResult && reducedResult.state,
sideEffects,
};
};
}