Skip to content

Commit f92f889

Browse files
committed
feat\!: improve FeatureFlag component API with predicate support
BREAKING CHANGE: Renamed `featureKey` prop to `flagKey` to avoid React key conflicts. Removed `negate` prop in favor of custom predicate functions. - Rename `featureKey` prop to `flagKey` to prevent conflicts with React's reserved key prop - Add optional `predicate` function prop for flexible custom matching logic - Remove `negate` prop (negation can be handled via predicate functions) - Improve type safety with generic types and FlagValue constraints - Add default `equals` predicate function for standard equality matching - Remove debug console.log statement - Support truthy evaluation when no match value is provided
1 parent 187eb41 commit f92f889

File tree

1 file changed

+45
-36
lines changed

1 file changed

+45
-36
lines changed

packages/react/src/declarative/FeatureFlag.tsx

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,76 @@
11
import React from 'react';
22
import { useFlag } from '../evaluation';
33
import type { FlagQuery } from '../query';
4+
import type { FlagValue, EvaluationDetails } from '@openfeature/core';
45

56
/**
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
811
*/
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> {
1021
/**
1122
* The key of the feature flag to evaluate.
1223
*/
13-
featureKey: string;
24+
flagKey: string;
1425

1526
/**
1627
* Optional value to match against the feature flag value.
1728
* If provided, the component will only render children when the flag value matches this value.
1829
* If a boolean, it will check if the flag is enabled (true) or disabled (false).
1930
* If a string, it will check if the flag variant equals this string.
2031
*/
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;
2242

2343
/**
2444
* Default value to use when the feature flag is not found.
2545
*/
26-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
27-
defaultValue: any;
46+
defaultValue: T;
2847

2948
/**
3049
* Content to render when the feature flag condition is met.
3150
* Can be a React node or a function that receives flag query details and returns a React node.
3251
*/
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);
3553

3654
/**
3755
* Optional content to render when the feature flag condition is not met.
3856
*/
3957
fallback?: React.ReactNode;
40-
41-
/**
42-
* If true, inverts the condition logic (renders children when condition is NOT met).
43-
*/
44-
negate?: boolean;
4558
}
4659

4760
/**
4861
* 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.
5163
* @returns {React.ReactElement | null} The rendered component or null if the feature is not enabled.
5264
*/
53-
export function FeatureFlag({
54-
featureKey,
65+
export function FeatureFlag<T extends FlagValue = FlagValue>({
66+
flagKey,
5567
match,
56-
negate = false,
57-
defaultValue = true,
68+
predicate,
69+
defaultValue,
5870
children,
5971
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, {
6274
updateOnContextChanged: true,
6375
});
6476

@@ -67,23 +79,20 @@ export function FeatureFlag({
6779
return <>{fallback}</>;
6880
}
6981

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);
7592
}
7693

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-
8494
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;
8796
return <>{childNode}</>;
8897
}
8998

0 commit comments

Comments
 (0)