Skip to content

feat: surface multivariate variant key and use it as the exposure value#401

Open
Zaimwa9 wants to merge 3 commits into
feat/event-processorfrom
feat/implement-variant-key
Open

feat: surface multivariate variant key and use it as the exposure value#401
Zaimwa9 wants to merge 3 commits into
feat/event-processorfrom
feat/implement-variant-key

Conversation

@Zaimwa9

@Zaimwa9 Zaimwa9 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Thanks for submitting a PR! Please check the boxes below:

  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature.
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

  • IFlagsmithFeature.variant — selected variant's key, "control" for the control bucket, omitted otherwise.
  • getExperimentFlag records the variant as the $flag_exposure value; flags without a variant record no exposure.
  • useExperiment re-fires the exposure when the variant changes.

How did you test this code?

Unit tests: variant parsing, exposure values, variant-change re-exposure

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Zaimwa9 Zaimwa9 requested a review from gagantrivedi June 10, 2026 16:03
@Zaimwa9 Zaimwa9 requested a review from a team as a code owner June 10, 2026 16:04
@Zaimwa9 Zaimwa9 requested review from kyle-ssg and removed request for a team June 10, 2026 16:04
@Zaimwa9 Zaimwa9 removed the request for review from kyle-ssg June 10, 2026 16:04
@Zaimwa9 Zaimwa9 marked this pull request as draft June 10, 2026 16:04

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for multivariate flag variants in the Flagsmith SDK. It updates the core SDK to extract and store the variant property on flags, uses the variant value instead of the flag value for experiment exposure tracking, and updates the React hooks (useFlags, useExperiment) to handle and trigger re-renders on variant changes. Additionally, types and tests are updated to support this new feature. Feedback from the review highlights two main areas of improvement: first, in flagsmith-core.ts, if a flag is missing (null), the current logic logs a misleading warning about a missing variant, so it is recommended to check if the flag exists before checking for its variant; second, in react.tsx, the use of .map() to mutate an object is an anti-pattern and should be refactored to use .forEach() for better clarity and performance.

Comment thread flagsmith-core.ts Outdated
Comment on lines +1000 to +1003
if (!flag?.variant) {
this.log(`Flagsmith: getExperimentFlag called for "${featureName}" which has no variant; experiments require a multivariate flag. No exposure recorded.`);
return flag;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If flag is null (e.g., if the feature name is misspelled or does not exist), !flag?.variant will evaluate to true. This causes the SDK to log a misleading warning stating that the feature has no variant and requires a multivariate flag, which is confusing since the feature itself is completely missing.

Consider checking if flag is null first and returning early without logging this warning.

Suggested change
if (!flag?.variant) {
this.log(`Flagsmith: getExperimentFlag called for "${featureName}" which has no variant; experiments require a multivariate flag. No exposure recorded.`);
return flag;
}
if (!flag) {
return null;
}
if (!flag.variant) {
this.log("Flagsmith: getExperimentFlag called for \"" + featureName + "\" which has no variant; experiments require a multivariate flag. No exposure recorded.");
return flag;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in b7847ac

Comment thread react.tsx
Comment on lines 182 to 190
flags
.map((k) => {
const variant = flagsmith!.getAllFlags()?.[k]?.variant
res[k] = {
enabled: flagsmith!.hasFeature(k),
value: flagsmith!.getValue(k),
...(variant != null ? { variant } : {}),
}
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using .map() to perform side-effects (mutating res) and chaining it with .concat() is an anti-pattern in JavaScript/TypeScript. .map() is intended for pure transformations and creates a new array that is immediately discarded here, leading to unnecessary memory allocation.

Consider refactoring the entire useMemo block to use forEach for clarity and efficiency:

    const res = useMemo(() => {
        const res: any = {}
        flags.forEach((k) => {
            const variant = flagsmith!.getAllFlags()?.[k]?.variant
            res[k] = {
                enabled: flagsmith!.hasFeature(k),
                value: flagsmith!.getValue(k),
                ...(variant != null ? { variant } : {}),
            }
        })
        traits?.forEach((v) => {
            res[v] = flagsmith!.getTrait(v)
        })
        return res
    }, [renderRef])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of scope for this one as this is legacy

@Zaimwa9 Zaimwa9 marked this pull request as ready for review June 11, 2026 09:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants