Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | /**
* Experimentation framework for feature flags and experiments.
*
* Uses Statsig under the hood, but abstracts the implementation details.
*
* Usage:
* import { useGate, useExperiment, useDynamicConfig, useLogEvent } from "experimentation";
*
* // Feature gates (boolean flags)
* const isFeatureEnabled = useGate("my_feature_gate");
*
* // Experiments (A/B tests with config values)
* const experimentValue = useExperiment("my_experiment", "variant", "control");
*
* // Dynamic configs (remote configuration)
* const configValue = useDynamicConfig("my_config", "setting", "default");
*
* // Event logging
* const logEvent = useLogEvent();
* logEvent("button_clicked", { button_name: "signup" });
*/
import {
useDynamicConfig as useStatsigDynamicConfig,
useExperiment as useStatsigExperiment,
useFeatureGate as useStatsigGate,
useStatsigClient,
} from "@statsig/react-bindings";
import { useCallback } from "react";
/**
* Check if experimentation is enabled.
* Returns false if the SDK key is not configured.
*/
export function isExperimentationEnabled(): boolean {
return !!process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY;
}
/**
* Check if all gates should pass (for development/testing).
* When enabled, useGate() always returns true.
*/
export function shouldPassAllGates(): boolean {
return process.env.NEXT_PUBLIC_STATSIG_PASS_ALL_GATES === "1";
}
/**
* Check if a feature gate is enabled for the current user.
*
* @param gateName - The name of the feature gate
* @returns true if the gate is enabled, false otherwise (including when experimentation is disabled).
* Returns true if NEXT_PUBLIC_STATSIG_PASS_ALL_GATES is enabled.
*
* @example
* const showNewFeature = useGate("new_feature_gate");
* if (showNewFeature) {
* return <NewFeature />;
* }
*/
export function useGate(gateName: string): boolean {
const { value } = useStatsigGate(gateName);
// Pass all gates for development/testing
Iif (shouldPassAllGates()) {
return true;
}
Iif (!isExperimentationEnabled()) {
return false;
}
return value;
}
/**
* Get a value from an experiment for the current user.
*
* @param experimentName - The name of the experiment
* @param parameterName - The parameter to retrieve from the experiment config
* @param defaultValue - Default value if experiment is not found or experimentation is disabled
* @returns The experiment parameter value or the default
*
* @example
* const buttonColor = useExperiment("button_color_test", "color", "blue");
* return <Button color={buttonColor}>Click me</Button>;
*/
export function useExperiment<T>(
experimentName: string,
parameterName: string,
defaultValue: T,
): T {
const { value } = useStatsigExperiment(experimentName);
Iif (!isExperimentationEnabled()) {
return defaultValue;
}
const paramValue = value[parameterName];
Iif (paramValue === undefined) {
return defaultValue;
}
return paramValue as T;
}
/**
* Get the full experiment config object for the current user.
*
* @param experimentName - The name of the experiment
* @returns The full experiment config object, or empty object if disabled
*
* @example
* const config = useExperimentConfig("onboarding_flow");
* const steps = config.steps ?? defaultSteps;
*/
export function useExperimentConfig(
experimentName: string,
): Record<string, unknown> {
const { value } = useStatsigExperiment(experimentName);
Iif (!isExperimentationEnabled()) {
return {};
}
return value;
}
/**
* Get a value from a dynamic config for the current user.
*
* @param configName - The name of the dynamic config
* @param parameterName - The parameter to retrieve from the config
* @param defaultValue - Default value if config is not found or experimentation is disabled
* @returns The config parameter value or the default
*
* @example
* const maxItems = useDynamicConfig("app_settings", "max_items", 10);
*/
export function useDynamicConfig<T>(
configName: string,
parameterName: string,
defaultValue: T,
): T {
const { value } = useStatsigDynamicConfig(configName);
Iif (!isExperimentationEnabled()) {
return defaultValue;
}
const paramValue = value[parameterName];
Iif (paramValue === undefined) {
return defaultValue;
}
return paramValue as T;
}
/**
* Get the full dynamic config object for the current user.
*
* @param configName - The name of the dynamic config
* @returns The full config object, or empty object if disabled
*
* @example
* const settings = useDynamicConfigValues("feature_settings");
*/
export function useDynamicConfigValues(
configName: string,
): Record<string, unknown> {
const { value } = useStatsigDynamicConfig(configName);
Iif (!isExperimentationEnabled()) {
return {};
}
return value;
}
/**
* Get a function to log custom events for analytics.
*
* @returns A function to log events, or a no-op if experimentation is disabled
*
* @example
* const logEvent = useLogEvent();
*
* const handleClick = () => {
* logEvent("button_clicked", "signup_button", { page: "home" });
* // ... handle click
* };
*/
export function useLogEvent(): (
eventName: string,
value?: string | number,
metadata?: Record<string, string>,
) => void {
const { client } = useStatsigClient();
return useCallback(
(
eventName: string,
value?: string | number,
metadata?: Record<string, string>,
) => {
Iif (!isExperimentationEnabled()) {
return;
}
client.logEvent(eventName, value, metadata);
},
[client],
);
}
|