-
Notifications
You must be signed in to change notification settings - Fork 191
/
Copy pathuseMergedState.ts
74 lines (63 loc) · 2.12 KB
/
useMergedState.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
import useEvent from './useEvent';
import { useLayoutUpdateEffect } from './useLayoutEffect';
import useState from './useState';
type Updater<T> = (
updater: T | ((origin: T) => T),
ignoreDestroy?: boolean,
) => void;
/** We only think `undefined` is empty */
function hasValue(value: any) {
return value !== undefined;
}
/**
* Similar to `useState` but will use props value if provided.
* Note that internal use rc-util `useState` hook.
*/
export default function useMergedState<T, R = T>(
defaultStateValue: T | (() => T),
option?: {
defaultValue?: T | (() => T);
value?: T;
onChange?: (value: T, prevValue: T) => void;
postState?: (value: T) => T;
},
): [R, Updater<T>] {
const { defaultValue, value, onChange, postState } = option || {};
// ======================= Init =======================
const [innerValue, setInnerValue] = useState<T>(() => {
if (hasValue(value)) {
return value;
} else if (hasValue(defaultValue)) {
return typeof defaultValue === 'function'
? (defaultValue as any)()
: defaultValue;
} else {
return typeof defaultStateValue === 'function'
? (defaultStateValue as any)()
: defaultStateValue;
}
});
const mergedValue = hasValue(value) ? value : innerValue;
const postMergedValue = postState ? postState(mergedValue) : mergedValue;
// ====================== Change ======================
const onChangeFn = useEvent(onChange);
const [prevValue, setPrevValue] = useState<[T]>([mergedValue]);
useLayoutUpdateEffect(() => {
const prev = prevValue[0];
if (innerValue !== prev) {
onChangeFn(innerValue, prev);
}
}, [prevValue]);
// Sync value back to `undefined` when it from control to un-control
useLayoutUpdateEffect(() => {
if (!hasValue(value)) {
setInnerValue(value);
}
}, [value]);
// ====================== Update ======================
const triggerChange: Updater<T> = useEvent((updater, ignoreDestroy) => {
setInnerValue(updater, ignoreDestroy);
setPrevValue([mergedValue], ignoreDestroy);
});
return [postMergedValue as unknown as R, triggerChange];
}