1
- /* eslint-disable react-hooks/rules-of-hooks */
2
1
/**
3
2
* Copyright 2018-2019, 2022-2024, Optimizely
4
3
*
14
13
* See the License for the specific language governing permissions and
15
14
* limitations under the License.
16
15
*/
17
- import { useCallback , useContext , useEffect , useState , useRef } from 'react' ;
16
+
17
+ import { useCallback , useContext , useEffect , useState , useRef , useMemo } from 'react' ;
18
18
19
19
import { UserAttributes , OptimizelyDecideOption , getLogger } from '@optimizely/optimizely-sdk' ;
20
20
@@ -449,35 +449,33 @@ export const useFeature: UseFeature = (featureKey, options = {}, overrides = {})
449
449
export const useDecision : UseDecision = ( flagKey , options = { } , overrides = { } ) => {
450
450
const { optimizely, isServerSide, timeout } = useContext ( OptimizelyContext ) ;
451
451
452
- if ( ! optimizely ) {
453
- hooksLogger . error (
454
- `Unable to use decision ${ flagKey } . optimizely prop must be supplied via a parent <OptimizelyProvider>`
455
- ) ;
456
- return [
452
+ const overrideAttrs = useCompareAttrsMemoize ( overrides . overrideAttributes ) ;
453
+
454
+ const defaultDecision = useMemo (
455
+ ( ) =>
457
456
createFailedDecision ( flagKey , 'Optimizely SDK not configured properly yet.' , {
458
- id : null ,
459
- attributes : { } ,
457
+ id : overrides . overrideUserId || null ,
458
+ attributes : overrideAttrs || { } ,
460
459
} ) ,
461
- false ,
462
- false ,
463
- ] ;
464
- }
460
+ [ flagKey , overrideAttrs , overrides . overrideUserId ]
461
+ ) ;
465
462
466
- const overrideAttrs = useCompareAttrsMemoize ( overrides . overrideAttributes ) ;
463
+ const getCurrentDecision : ( ) => { decision : OptimizelyDecision } = useCallback (
464
+ ( ) => ( {
465
+ decision :
466
+ optimizely ?. decide ( flagKey , options . decideOptions , overrides . overrideUserId , overrideAttrs ) || defaultDecision ,
467
+ } ) ,
468
+ [ flagKey , defaultDecision , optimizely , options . decideOptions , overrideAttrs , overrides . overrideUserId ]
469
+ ) ;
467
470
468
- const getCurrentDecision : ( ) => { decision : OptimizelyDecision } = ( ) => ( {
469
- decision : optimizely . decide ( flagKey , options . decideOptions , overrides . overrideUserId , overrideAttrs ) ,
470
- } ) ;
471
+ const isClientReady = isServerSide || ! ! optimizely ?. isReady ( ) ;
472
+ const isReadyPromiseFulfilled = ! ! optimizely ?. getIsReadyPromiseFulfilled ( ) ;
471
473
472
- const isClientReady = isServerSide || optimizely . isReady ( ) ;
473
474
const [ state , setState ] = useState < { decision : OptimizelyDecision } & InitializationState > ( ( ) => {
474
475
const decisionState = isClientReady
475
476
? getCurrentDecision ( )
476
477
: {
477
- decision : createFailedDecision ( flagKey , 'Optimizely SDK not configured properly yet.' , {
478
- id : overrides . overrideUserId || null ,
479
- attributes : overrideAttrs ,
480
- } ) ,
478
+ decision : defaultDecision ,
481
479
} ;
482
480
return {
483
481
...decisionState ,
@@ -504,21 +502,23 @@ export const useDecision: UseDecision = (flagKey, options = {}, overrides = {})
504
502
}
505
503
506
504
const finalReadyTimeout = options . timeout !== undefined ? options . timeout : timeout ;
505
+
507
506
useEffect ( ( ) => {
508
507
// Subscribe to initialzation promise only
509
508
// 1. When client is using Sdk Key, which means the initialization will be asynchronous
510
509
// and we need to wait for the promise and update decision.
511
510
// 2. When client is using datafile only but client is not ready yet which means user
512
511
// was provided as a promise and we need to subscribe and wait for user to become available.
513
- if ( optimizely . getIsUsingSdkKey ( ) || ! isClientReady ) {
512
+ if ( optimizely && ( optimizely . getIsUsingSdkKey ( ) || ! isClientReady ) ) {
514
513
subscribeToInitialization ( optimizely , finalReadyTimeout , ( initState ) => {
515
514
setState ( {
516
515
...getCurrentDecision ( ) ,
517
516
...initState ,
518
517
} ) ;
519
518
} ) ;
520
519
}
521
- } , [ ] ) ;
520
+ // eslint-disable-next-line react-hooks/exhaustive-deps
521
+ } , [ finalReadyTimeout , getCurrentDecision , optimizely ] ) ;
522
522
523
523
useEffect ( ( ) => {
524
524
if ( overrides . overrideUserId || overrides . overrideAttributes || ! options . autoUpdate ) {
@@ -532,11 +532,11 @@ export const useDecision: UseDecision = (flagKey, options = {}, overrides = {})
532
532
...getCurrentDecision ( ) ,
533
533
} ) ) ;
534
534
} ) ;
535
- } , [ overrides . overrideUserId , overrides . overrideAttributes , options . autoUpdate ] ) ;
535
+ } , [ overrides . overrideUserId , overrides . overrideAttributes , options . autoUpdate , flagKey , getCurrentDecision ] ) ;
536
536
537
537
useEffect ( ( ) => {
538
538
// Subscribe to update after first datafile is fetched and readyPromise is resolved to avoid redundant rendering.
539
- if ( optimizely . getIsReadyPromiseFulfilled ( ) && options . autoUpdate ) {
539
+ if ( optimizely && isReadyPromiseFulfilled && options . autoUpdate ) {
540
540
return setupAutoUpdateListeners ( optimizely , HookType . FEATURE , flagKey , hooksLogger , ( ) => {
541
541
setState ( ( prevState ) => ( {
542
542
...prevState ,
@@ -545,14 +545,20 @@ export const useDecision: UseDecision = (flagKey, options = {}, overrides = {})
545
545
} ) ;
546
546
}
547
547
return ( ) : void => { } ;
548
- } , [ optimizely . getIsReadyPromiseFulfilled ( ) , options . autoUpdate , optimizely , flagKey , getCurrentDecision ] ) ;
548
+ } , [ isReadyPromiseFulfilled , options . autoUpdate , optimizely , flagKey , getCurrentDecision ] ) ;
549
+
550
+ if ( ! optimizely ) {
551
+ hooksLogger . error (
552
+ `Unable to use decision ${ flagKey } . optimizely prop must be supplied via a parent <OptimizelyProvider>`
553
+ ) ;
554
+ }
549
555
550
556
return [ state . decision , state . clientReady , state . didTimeout ] ;
551
557
} ;
552
558
553
559
export const useTrackEvent : UseTrackEvent = ( ) => {
554
560
const { optimizely, isServerSide, timeout } = useContext ( OptimizelyContext ) ;
555
- const isClientReady = ! ! ( isServerSide || optimizely ?. isReady ( ) ) ;
561
+ const isClientReady = isServerSide || ! ! optimizely ?. isReady ( ) ;
556
562
557
563
const track = useCallback (
558
564
( ...rest : Parameters < ReactSDKClient [ 'track' ] > ) : void => {
@@ -569,10 +575,6 @@ export const useTrackEvent: UseTrackEvent = () => {
569
575
[ optimizely , isClientReady ]
570
576
) ;
571
577
572
- if ( ! optimizely ) {
573
- return [ track , false , false ] ;
574
- }
575
-
576
578
const [ state , setState ] = useState < {
577
579
clientReady : boolean ;
578
580
didTimeout : DidTimeout ;
@@ -589,12 +591,13 @@ export const useTrackEvent: UseTrackEvent = () => {
589
591
// and we need to wait for the promise and update decision.
590
592
// 2. When client is using datafile only but client is not ready yet which means user
591
593
// was provided as a promise and we need to subscribe and wait for user to become available.
592
- if ( optimizely . getIsUsingSdkKey ( ) || ! isClientReady ) {
594
+ if ( optimizely && ( optimizely . getIsUsingSdkKey ( ) || ! isClientReady ) ) {
593
595
subscribeToInitialization ( optimizely , timeout , ( initState ) => {
594
596
setState ( initState ) ;
595
597
} ) ;
596
598
}
597
- } , [ ] ) ;
599
+ // eslint-disable-next-line react-hooks/exhaustive-deps
600
+ } , [ optimizely , timeout ] ) ;
598
601
599
602
return [ track , state . clientReady , state . didTimeout ] ;
600
603
} ;
0 commit comments