-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.ts
125 lines (108 loc) · 3.39 KB
/
index.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
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
import { useState, useEffect, useReducer, useCallback } from 'react';
import {
Scheduler,
Awaited,
Optimistic,
Reducer,
ReducerState,
Dispatch,
ReducerAction,
} from './types';
import { useImmer } from 'use-immer';
function useOptimisticReducer<R extends Reducer<any, any>>(
reducer: R,
initializerArg: ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>] {
const [awaited, setAwaited] = useState<Awaited>({ key: null });
const [scheduler, setScheduler] = useImmer<Scheduler<R> | any>({});
const [state, dispatch] = useReducer(reducer, initializerArg);
useEffect(() => {
(async () => {
for (const key in scheduler) {
const optimistic = scheduler[key];
// If queue is waiting to be called
if (!optimistic.isCompleted && !optimistic.isFetching) {
// Start fetching
setScheduler((draft) => {
draft[key].isFetching = true;
});
try {
await optimistic.queue[0].callback();
setAwaited({ key });
} catch (e) {
// Retrieve previous state
const { prevState } = scheduler[key];
// Execute fallback if provided
const { fallback } = scheduler[key].queue[0];
if (typeof fallback !== 'undefined') {
fallback(prevState);
}
// Reset scheduler
setScheduler((draft) => {
draft[key] = {
queue: [],
isFetching: false,
isCompleted: true,
prevState: {},
};
});
}
}
}
})();
}, [scheduler]);
useEffect(() => {
if (awaited.key) {
nextSchedule(awaited.key);
}
}, [awaited]);
const nextSchedule = useCallback(
(key: string) => {
const nextQueue = scheduler[key].queue.slice(1);
setScheduler((draft) => {
draft[key].queue = nextQueue;
draft[key].isFetching = false;
draft[key].isCompleted = !nextQueue.length;
});
},
[scheduler]
);
function customDispatch(action: ReducerAction<R>): void {
// Extract the optimistic property from a cloned action
const clonedAction: ReducerAction<R> = Object.assign({}, action);
delete clonedAction.optimistic;
// Update the UI without sending the optimistic property
dispatch(clonedAction);
// If action is dispatched optimistically
const optimistic: Optimistic<R> = action.optimistic;
if (typeof optimistic === 'object') {
/**
* If a specific queue is included within the optimistic object,
* the actions will be executed in a separate queue.
* If no queue is specified, the action type will be used by default.
*/
const key: string = optimistic.queue ?? action.type;
// Schedule callback
if (key in scheduler) {
// Append action into the existing queue
setScheduler((draft) => {
draft[key].queue.push(optimistic);
draft[key].isCompleted = false;
draft[key].prevState = state;
});
} else {
// Add action to a new queue
setScheduler((draft) => {
draft[key] = {
queue: [optimistic],
isFetching: false,
isCompleted: false,
prevState: state,
};
});
}
}
}
return [state, customDispatch];
}
export default useOptimisticReducer;