1
1
import React from 'react' ;
2
2
import { useFlag } from '../evaluation' ;
3
3
import type { FlagQuery } from '../query' ;
4
+ import type { FlagValue , EvaluationDetails } from '@openfeature/core' ;
4
5
5
6
/**
6
- * Props for the Feature component that conditionally renders content based on feature flag state.
7
- * @interface FeatureProps
7
+ * Default predicate function that checks if the expected value equals the actual flag value.
8
+ * @param {T } expected The expected value to match against
9
+ * @param {EvaluationDetails<T> } actual The evaluation details containing the actual flag value
10
+ * @returns {boolean } true if the values match, false otherwise
8
11
*/
9
- interface FeatureProps {
12
+ function equals < T extends FlagValue > ( expected : T , actual : EvaluationDetails < T > ) : boolean {
13
+ return expected === actual . value ;
14
+ }
15
+
16
+ /**
17
+ * Props for the FeatureFlag component that conditionally renders content based on feature flag state.
18
+ * @interface FeatureFlagProps
19
+ */
20
+ interface FeatureFlagProps < T extends FlagValue = FlagValue > {
10
21
/**
11
22
* The key of the feature flag to evaluate.
12
23
*/
13
- featureKey : string ;
24
+ flagKey : string ;
14
25
15
26
/**
16
27
* Optional value to match against the feature flag value.
17
28
* If provided, the component will only render children when the flag value matches this value.
18
29
* If a boolean, it will check if the flag is enabled (true) or disabled (false).
19
30
* If a string, it will check if the flag variant equals this string.
20
31
*/
21
- match ?: string | boolean ;
32
+ match ?: T ;
33
+
34
+ /**
35
+ * Optional predicate function for custom matching logic.
36
+ * If provided, this function will be used instead of the default equality check.
37
+ * @param expected The expected value (from match prop)
38
+ * @param actual The evaluation details
39
+ * @returns true if the condition is met, false otherwise
40
+ */
41
+ predicate ?: ( expected : T | undefined , actual : EvaluationDetails < T > ) => boolean ;
22
42
23
43
/**
24
44
* Default value to use when the feature flag is not found.
25
45
*/
26
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
- defaultValue : any ;
46
+ defaultValue : T ;
28
47
29
48
/**
30
49
* Content to render when the feature flag condition is met.
31
50
* Can be a React node or a function that receives flag query details and returns a React node.
32
51
*/
33
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
- children : React . ReactNode | ( ( details : FlagQuery < any > ) => React . ReactNode ) ;
52
+ children : React . ReactNode | ( ( details : FlagQuery < T > ) => React . ReactNode ) ;
35
53
36
54
/**
37
55
* Optional content to render when the feature flag condition is not met.
38
56
*/
39
57
fallback ?: React . ReactNode ;
40
-
41
- /**
42
- * If true, inverts the condition logic (renders children when condition is NOT met).
43
- */
44
- negate ?: boolean ;
45
58
}
46
59
47
60
/**
48
61
* FeatureFlag component that conditionally renders its children based on the evaluation of a feature flag.
49
- *
50
- * @param {FeatureProps } props The properties for the FeatureFlag component.
62
+ * @param {FeatureFlagProps } props The properties for the FeatureFlag component.
51
63
* @returns {React.ReactElement | null } The rendered component or null if the feature is not enabled.
52
64
*/
53
- export function FeatureFlag ( {
54
- featureKey ,
65
+ export function FeatureFlag < T extends FlagValue = FlagValue > ( {
66
+ flagKey ,
55
67
match,
56
- negate = false ,
57
- defaultValue = true ,
68
+ predicate ,
69
+ defaultValue,
58
70
children,
59
71
fallback = null ,
60
- } : FeatureProps ) : React . ReactElement | null {
61
- const details = useFlag ( featureKey , defaultValue , {
72
+ } : FeatureFlagProps < T > ) : React . ReactElement | null {
73
+ const details = useFlag ( flagKey , defaultValue , {
62
74
updateOnContextChanged : true ,
63
75
} ) ;
64
76
@@ -67,23 +79,20 @@ export function FeatureFlag({
67
79
return < > { fallback } </ > ;
68
80
}
69
81
70
- let isMatch = false ;
71
- if ( typeof match === 'string' ) {
72
- isMatch = details . variant === match ;
73
- } else if ( typeof match !== 'undefined' ) {
74
- isMatch = details . value === match ;
82
+ // Use custom predicate if provided, otherwise use default matching logic
83
+ let shouldRender = false ;
84
+ if ( predicate ) {
85
+ shouldRender = predicate ( match , details . details as EvaluationDetails < T > ) ;
86
+ } else if ( match !== undefined ) {
87
+ // Default behavior: check if match value equals flag value
88
+ shouldRender = equals ( match , details . details as EvaluationDetails < T > ) ;
89
+ } else {
90
+ // If no match value is provided, render if flag is truthy
91
+ shouldRender = Boolean ( details . value ) ;
75
92
}
76
93
77
- // If match is undefined, we assume the flag is enabled
78
- if ( match === void 0 ) {
79
- isMatch = true ;
80
- }
81
-
82
- const shouldRender = negate ? ! isMatch : isMatch ;
83
-
84
94
if ( shouldRender ) {
85
- console . log ( 'chop chop' ) ;
86
- const childNode : React . ReactNode = typeof children === 'function' ? children ( details ) : children ;
95
+ const childNode : React . ReactNode = typeof children === 'function' ? children ( details as FlagQuery < T > ) : children ;
87
96
return < > { childNode } </ > ;
88
97
}
89
98
0 commit comments