1
1
const { useEffect, useMemo, useRef, useState} = require ( "react" ) ;
2
2
const { CPromise, CanceledError, E_REASON_UNMOUNTED } = require ( "c-promise2" ) ;
3
- const isEqualObjects = require ( 'is-equal-objects/dist/is-equal-objects.cjs ' ) ;
3
+ const { isEqualObjects, cloneObject } = require ( 'is-equal-objects' ) ;
4
4
5
5
const { E_REASON_QUEUE_OVERFLOW , E_REASON_RESTART } = CanceledError . registerErrors ( {
6
6
E_REASON_QUEUE_OVERFLOW : 'overflow' ,
@@ -51,6 +51,7 @@ const removeElement = (arr, element) => {
51
51
* @param {deps } [options.deps= []]
52
52
* @param {boolean } [options.skipFirst= false]
53
53
* @param {boolean } [options.states= false]
54
+ * @param {boolean } [options.once= false]
54
55
* @returns {UseAsyncEffectCancelFn }
55
56
*/
56
57
const useAsyncEffect = ( generator , options ) => {
@@ -59,16 +60,19 @@ const useAsyncEffect = (generator, options) => {
59
60
let {
60
61
deps = [ ] ,
61
62
skipFirst= false ,
62
- states = false
63
+ states = false ,
64
+ once = false
63
65
} = options && Array . isArray ( options ) ? { deps : options } : options || { } ;
64
66
65
- const [ state , setState ] = states ? useAsyncDeepState ( {
67
+ const initialState = {
66
68
done : false ,
67
69
result : undefined ,
68
70
error : undefined ,
69
71
canceled : false ,
70
72
paused : false
71
- } , { watch : false } ) : [ ] ;
73
+ } ;
74
+
75
+ const [ state , setState ] = states ? useAsyncDeepState ( initialState , { watch : false } ) : [ ] ;
72
76
73
77
if ( ! isGeneratorFn ( generator ) ) {
74
78
throw TypeError ( 'useAsyncEffect requires a generator as the first argument' ) ;
@@ -100,8 +104,6 @@ const useAsyncEffect = (generator, options) => {
100
104
return cancel ;
101
105
} ) ;
102
106
103
-
104
-
105
107
useEffect ( ( ) => {
106
108
if ( ! current . inited ) {
107
109
current . inited = true ;
@@ -110,10 +112,19 @@ const useAsyncEffect = (generator, options) => {
110
112
}
111
113
}
112
114
115
+ if ( once && current . done ) {
116
+ return ;
117
+ }
118
+
113
119
let cb ;
114
120
121
+ states && setState ( initialState ) ;
122
+
115
123
let promise = current . promise = CPromise . run ( generator , { resolveSignatures : true , scopeArg : true } )
116
124
. then ( result => {
125
+
126
+ current . done = true ;
127
+
117
128
if ( typeof result === 'function' ) {
118
129
cb = result ;
119
130
states && setState ( {
@@ -464,112 +475,131 @@ const useFactory = (factory, args) => {
464
475
465
476
initialized . add ( current ) ;
466
477
467
- Object . assign ( current , factory . apply ( null , args ) ) ;
468
-
469
- return current ;
478
+ return Object . assign ( current , factory . apply ( null , args ) ) ;
470
479
}
471
480
472
- const assignState = ( obj , target ) => {
473
- Object . assign ( obj , target ) ;
481
+ const assignEnumerableProps = ( source , target ) => {
482
+ Object . assign ( source , target ) ;
474
483
const symbols = Object . getOwnPropertySymbols ( target ) ;
475
484
let i = symbols . length ;
476
485
while ( i -- > 0 ) {
477
- const symbol = symbols [ i ] ;
478
- obj [ symbol ] = target [ symbol ] ;
486
+ let symbol = symbols [ i ] ;
487
+ source [ symbol ] = target [ symbol ] ;
479
488
}
480
- return obj ;
489
+ return source ;
481
490
}
482
491
483
- class ProtoState {
484
- constructor ( initialState ) {
485
- initialState && assignState ( this , initialState ) ;
486
- }
492
+ const protoState = Object . create ( null , {
493
+ toJSON : {
494
+ value : function toJSON ( ) {
495
+ const obj = { } ;
496
+ let target = this ;
497
+ do {
498
+ assignEnumerableProps ( obj , target ) ;
499
+ } while ( ( target = Object . getPrototypeOf ( target ) ) && target !== Object . prototype ) ;
500
+ return obj ;
501
+ }
502
+ } ,
487
503
488
- toJSON ( ) {
489
- const obj = { } ;
490
- let target = this ;
491
- do {
492
- assignState ( obj , target ) ;
493
- } while ( ( target = Object . getPrototypeOf ( target ) ) && target !== Object . prototype ) ;
494
- return obj ;
504
+ [ isEqualObjects . plainObject ] : {
505
+ value : true
495
506
}
496
- }
507
+ } )
508
+
509
+ const getAllKeys = ( obj ) => Object . keys ( obj ) . concat ( Object . getOwnPropertyNames ( obj ) ) ;
497
510
498
511
/**
499
512
* useAsyncDeepState hook whose setter returns a promise
500
- * @param {* } [initialValue ]
513
+ * @param {* } [initialState ]
501
514
* @param {Boolean } [watch= true]
515
+ * @param {Boolean } [defineSetters= true]
502
516
* @returns {[any, function(*=, boolean=): (Promise<*>|undefined)] }
503
517
*/
504
- const useAsyncDeepState = ( initialValue , { watch= true } = { } ) => {
505
- const { current} = useRef ( { } ) ;
518
+ const useAsyncDeepState = ( initialState , { watch = true , defineSetters = true } = { } ) => {
506
519
507
- if ( ! current . inited ) {
508
- if ( initialValue !== undefined && typeof initialValue !== "object" ) {
520
+ const current = useFactory ( ( ) => {
521
+ if ( initialState !== undefined && typeof initialState !== "object" ) {
509
522
throw TypeError ( 'initial state must be a plain object' ) ;
510
523
}
511
524
512
- const state = new ProtoState ( initialValue ) ;
525
+ const setter = ( patch , scope , cb ) => {
526
+ setState ( ( state ) => {
527
+ if ( typeof patch === 'function' ) {
528
+ patch = patch ( state ) ;
529
+ }
530
+
531
+ if ( patch !== true && patch != null && typeof patch !== 'object' ) {
532
+ throw new TypeError ( 'patch must be a plain object or boolean' ) ;
533
+ }
534
+
535
+ if (
536
+ patch !== true &&
537
+ ( patch === null || assignEnumerableProps ( current . state , patch ) ) &&
538
+ ( ! current . stateChanged && isEqualObjects ( current . state , current . snapshot ) )
539
+ ) {
540
+ scope && cb ( state ) ;
541
+ return state ;
542
+ }
543
+
544
+ current . stateChanged = true ;
513
545
514
- Object . assign ( current , {
546
+ if ( scope ) {
547
+ current . callbacks . set ( scope , cb ) ;
548
+ scope . onDone ( ( ) => current . callbacks . delete ( scope ) )
549
+ }
550
+
551
+ return Object . freeze ( Object . create ( current . proxy ) ) ;
552
+ } ) ;
553
+ }
554
+
555
+ const state = assignEnumerableProps ( Object . create ( protoState ) , initialState )
556
+
557
+ const proxy = Object . create ( state , defineSetters && getAllKeys ( initialState )
558
+ . reduce ( ( props , prop ) => {
559
+ props [ prop ] = {
560
+ get ( ) {
561
+ return state [ prop ] ;
562
+ } ,
563
+ set ( value ) {
564
+ state [ prop ] = value ;
565
+ setter ( null ) ;
566
+ }
567
+ }
568
+ return props ;
569
+ } , { } ) ) ;
570
+
571
+ return {
515
572
state,
516
- oldState : new ProtoState ( initialValue ) ,
517
- callbacks : new Map ( )
518
- } )
519
- }
573
+ snapshot : null ,
574
+ proxy,
575
+ initialState : Object . freeze ( Object . create ( proxy ) ) ,
576
+ stateChanged : false ,
577
+ callbacks : new Map ( ) ,
578
+ setter
579
+ }
580
+ } ) ;
520
581
521
- const [ state , setState ] = useState ( ! current . inited && Object . freeze ( Object . create ( current . state ) ) ) ;
582
+ const [ state , setState ] = useState ( current . initialState ) ;
522
583
523
- watch && useEffect ( ( ) => {
524
- if ( ! current . inited ) return ;
525
- const data = [ state , current . oldState ] ;
526
- current . callbacks . forEach ( cb => cb ( data ) ) ;
584
+ useEffect ( ( ) => {
585
+ current . stateChanged = false ;
586
+ current . callbacks . forEach ( cb => cb ( state ) ) ;
527
587
current . callbacks . clear ( ) ;
528
- current . oldState = new ProtoState ( current . state ) ;
588
+ current . snapshot = cloneObject ( current . state ) ;
529
589
} , [ state ] ) ;
530
590
531
- current . inited = true ;
532
-
533
591
return [
534
592
state ,
535
593
/**
536
594
* state async accessor
537
- * @param {Object } [newState]
595
+ * @param {Object } [handlerOrPatch]
596
+ * @param {Boolean } [watchChanges= true]
538
597
* @returns {Promise<*>|undefined }
539
598
*/
540
- function ( newState ) {
541
- const setter = ( scope , cb ) => {
542
- if ( typeof newState === 'function' ) {
543
- newState = newState ( current . state ) ;
544
- }
545
-
546
- if ( newState != null && typeof newState !== 'object' ) {
547
- throw new TypeError ( 'new state must be a plain object' ) ;
548
- }
549
-
550
- setState ( ( state ) => {
551
- if ( newState == null || isEqualObjects ( assignState ( current . state , newState ) , current . oldState ) ) {
552
- scope && cb ( [ state , current . oldState ] ) ;
553
- return state ;
554
- }
555
-
556
- if ( scope ) {
557
- current . callbacks . set ( scope , cb ) ;
558
- scope . onDone ( ( ) => current . callbacks . delete ( scope ) )
559
- }
560
-
561
- return Object . freeze ( Object . create ( current . state ) ) ;
562
- } ) ;
563
- }
564
-
565
- if ( ! watch ) {
566
- setter ( ) ;
567
- return ;
568
- }
569
-
570
- return new CPromise ( ( resolve , reject , scope ) => {
571
- setter ( scope , resolve ) ;
572
- } ) ;
599
+ function ( handlerOrPatch , watchChanges = watch ) {
600
+ return watchChanges ? new CPromise ( ( resolve , reject , scope ) => {
601
+ current . setter ( handlerOrPatch , scope , resolve ) ;
602
+ } ) : current . setter ( handlerOrPatch ) ;
573
603
}
574
604
]
575
605
}
0 commit comments