@@ -8,6 +8,28 @@ type Updater<T> = (
88 ignoreDestroy ?: boolean ,
99) => void ;
1010
11+ enum Source {
12+ INNER ,
13+ PROP ,
14+ }
15+
16+ type ValueRecord < T > = [ T , Source , T ] ;
17+
18+ const useUpdateEffect : typeof React . useEffect = ( callback , deps ) => {
19+ const [ firstMount , setFirstMount ] = React . useState ( true ) ;
20+
21+ useLayoutEffect ( ( ) => {
22+ if ( ! firstMount ) {
23+ return callback ( ) ;
24+ }
25+ } , deps ) ;
26+
27+ // We tell react that first mount has passed
28+ useLayoutEffect ( ( ) => {
29+ setFirstMount ( false ) ;
30+ } , [ ] ) ;
31+ } ;
32+
1133/**
1234 * Similar to `useState` but will use props value if provided.
1335 * Note that internal use rc-util `useState` hook.
@@ -22,53 +44,77 @@ export default function useMergedState<T, R = T>(
2244 } ,
2345) : [ R , Updater < T > ] {
2446 const { defaultValue, value, onChange, postState } = option || { } ;
25- const [ innerValue , setInnerValue ] = useState < T > ( ( ) => {
47+
48+ // ======================= Init =======================
49+ const [ mergedValue , setMergedValue ] = useState < ValueRecord < T > > ( ( ) => {
50+ let finalValue : T = undefined ;
51+ let source : Source ;
52+
2653 if ( value !== undefined ) {
27- return value ;
28- }
29- if ( defaultValue !== undefined ) {
30- return typeof defaultValue === 'function'
31- ? ( defaultValue as any ) ( )
32- : defaultValue ;
54+ finalValue = value ;
55+ source = Source . PROP ;
56+ } else if ( defaultValue !== undefined ) {
57+ finalValue =
58+ typeof defaultValue === 'function'
59+ ? ( defaultValue as any ) ( )
60+ : defaultValue ;
61+ source = Source . PROP ;
62+ } else {
63+ finalValue =
64+ typeof defaultStateValue === 'function'
65+ ? ( defaultStateValue as any ) ( )
66+ : defaultStateValue ;
67+ source = Source . INNER ;
3368 }
34- return typeof defaultStateValue === 'function'
35- ? ( defaultStateValue as any ) ( )
36- : defaultStateValue ;
69+
70+ return [ finalValue , source , finalValue ] ;
3771 } ) ;
3872
39- const mergedValue = value !== undefined ? value : innerValue ;
40- const postMergedValue = postState ? postState ( mergedValue ) : mergedValue ;
73+ const postMergedValue = postState
74+ ? postState ( mergedValue [ 0 ] )
75+ : mergedValue [ 0 ] ;
4176
42- // setState
43- const onChangeFn = useEvent ( onChange ) ;
77+ // ======================= Sync =======================
78+ useUpdateEffect ( ( ) => {
79+ setMergedValue ( ( [ prevValue ] ) => [ value , Source . PROP , prevValue ] ) ;
80+ } , [ value ] ) ;
4481
45- const [ changePrevValue , setChangePrevValue ] = useState < T > ( ) ;
82+ // ====================== Update ======================
83+ const changeEventPrevRef = React . useRef < T > ( ) ;
4684
4785 const triggerChange : Updater < T > = useEvent ( ( updater , ignoreDestroy ) => {
48- setChangePrevValue ( mergedValue , true ) ;
49- setInnerValue ( prev => {
50- const nextValue =
51- typeof updater === 'function' ? ( updater as any ) ( prev ) : updater ;
52- return nextValue ;
86+ setMergedValue ( prev => {
87+ const [ prevValue , prevSource , prevPrevValue ] = prev ;
88+
89+ const nextValue : T =
90+ typeof updater === 'function' ? ( updater as any ) ( prevValue ) : updater ;
91+
92+ // Do nothing if value not change
93+ if ( nextValue === prevValue ) {
94+ return prev ;
95+ }
96+
97+ // Use prev prev value if is in a batch update to avoid missing data
98+ const overridePrevValue =
99+ prevSource === Source . INNER &&
100+ changeEventPrevRef . current !== prevPrevValue
101+ ? prevPrevValue
102+ : prevValue ;
103+
104+ return [ nextValue , Source . INNER , overridePrevValue ] ;
53105 } , ignoreDestroy ) ;
54106 } ) ;
55107
56- // Effect to trigger onChange
57- useLayoutEffect ( ( ) => {
58- if ( changePrevValue !== undefined && changePrevValue !== innerValue ) {
59- onChangeFn ?.( innerValue , changePrevValue ) ;
60- }
61- } , [ changePrevValue , innerValue , onChangeFn ] ) ;
108+ // ====================== Change ======================
109+ const onChangeFn = useEvent ( onChange ) ;
62110
63- // Effect of reset value to `undefined`
64- const prevValueRef = React . useRef ( value ) ;
65- React . useEffect ( ( ) => {
66- if ( value === undefined && value !== prevValueRef . current ) {
67- setInnerValue ( value ) ;
111+ useLayoutEffect ( ( ) => {
112+ const [ current , source , prev ] = mergedValue ;
113+ if ( current !== prev && source === Source . INNER ) {
114+ onChangeFn ( current , prev ) ;
115+ changeEventPrevRef . current = prev ;
68116 }
69-
70- prevValueRef . current = value ;
71- } , [ value ] ) ;
117+ } , [ mergedValue ] ) ;
72118
73119 return [ postMergedValue as unknown as R , triggerChange ] ;
74120}
0 commit comments