We use cookies to enhance your experience on the site
CodeWorlds

Feature Flags and A/B Testing in React -- Controlled deployments

On a spaceship, before activating a new navigation system for the entire fleet, you test it on one ship. If it works -- you deploy it to the rest. If not -- you turn it off with a single switch. This is exactly what Feature Flags are -- a mechanism that allows enabling and disabling features without deploying new code.

What are Feature Flags?

A Feature Flag (feature toggle) is a boolean value or configuration that determines whether a given feature is available:

1// Simplest form -- object with flags
2const featureFlags = {
3  newDashboard: true,
4  darkMode: true,
5  experimentalNav: false,
6  betaSearch: false,
7};
8
9// Usage in a component
10function App() {
11  return (
12    <div>
13      {featureFlags.newDashboard
14        ? <NewDashboard />
15        : <OldDashboard />
16      }
17      {featureFlags.betaSearch && <BetaSearchBar />}
18    </div>
19  );
20}

Feature Flag Provider

In a real application, we store flags in context so they're available everywhere:

1const FeatureFlagContext = createContext({});
2
3function FeatureFlagProvider({ flags, children }) {
4  return (
5    <FeatureFlagContext.Provider value={flags}>
6      {children}
7    </FeatureFlagContext.Provider>
8  );
9}
10
11function useFeatureFlag(flagName) {
12  const flags = useContext(FeatureFlagContext);
13  return flags[flagName] ?? false;
14}
15
16// Usage
17function NavigationBar() {
18  const showNewNav = useFeatureFlag('newNavigation');
19
20  return showNewNav ? <NewNavBar /> : <ClassicNavBar />;
21}

Feature component -- declarative flags

Instead of writing if/else in every component, we can create a declarative component:

1function Feature({ name, children, fallback = null }) {
2  const isEnabled = useFeatureFlag(name);
3  return isEnabled ? children : fallback;
4}
5
6// Usage -- readable and declarative
7function MissionControl() {
8  return (
9    <div>
10      <h1>Mission Control</h1>
11
12      <Feature name="advancedRadar">
13        <AdvancedRadarPanel />
14      </Feature>
15
16      <Feature
17        name="newStatusBar"
18        fallback={<LegacyStatusBar />}
19      >
20        <ModernStatusBar />
21      </Feature>
22
23      <Feature name="experimentalWarpDrive">
24        <WarpDriveControls />
25      </Feature>
26    </div>
27  );
28}

A/B Testing -- variants for different users

A/B Testing is an extension of feature flags where different users see different versions of the interface:

1function useABTest(testName, variants) {
2  const [variant, setVariant] = useState(null);
3
4  useEffect(() => {
5    // Check if user already has an assigned variant
6    const stored = localStorage.getItem(`ab_${testName}`);
7    if (stored && variants.includes(stored)) {
8      setVariant(stored);
9    } else {
10      // Randomly assign a variant
11      const randomVariant = variants[
12        Math.floor(Math.random() * variants.length)
13      ];
14      localStorage.setItem(`ab_${testName}`, randomVariant);
15      setVariant(randomVariant);
16    }
17  }, [testName, variants]);
18
19  return variant;
20}
21
22// Usage
23function LandingPage() {
24  const variant = useABTest('landing_cta', ['control', 'variant_a', 'variant_b']);
25
26  if (variant === 'control') {
27    return <ClassicHero ctaText="Start your mission" />;
28  }
29  if (variant === 'variant_a') {
30    return <ClassicHero ctaText="Join the fleet!" />;
31  }
32  return <ModernHero ctaText="Launch now" />;
33}

Percentage Rollout -- gradual deployments

Instead of enabling a flag for everyone at once, you can deploy gradually -- e.g., 10% of users, then 50%, then 100%:

1function usePercentageRollout(featureName, percentage) {
2  const [isEnabled, setIsEnabled] = useState(false);
3
4  useEffect(() => {
5    // Deterministic hash based on userId + featureName
6    const stored = localStorage.getItem(`rollout_${featureName}`);
7    if (stored !== null) {
8      setIsEnabled(stored === 'true');
9    } else {
10      const random = Math.random() * 100;
11      const enabled = random < percentage;
12      localStorage.setItem(`rollout_${featureName}`, String(enabled));
13      setIsEnabled(enabled);
14    }
15  }, [featureName, percentage]);
16
17  return isEnabled;
18}
19
20// Usage
21function Dashboard() {
22  const showNewChart = usePercentageRollout('new_chart_v2', 30); // 30% of users
23
24  return (
25    <div>
26      {showNewChart ? <NewInteractiveChart /> : <LegacyChart />}
27    </div>
28  );
29}

Dynamic flags from the server

In production, flags often come from the server (LaunchDarkly, Flagsmith, Unleash):

1function useRemoteFlags() {
2  const [flags, setFlags] = useState({});
3  const [loading, setLoading] = useState(true);
4
5  useEffect(() => {
6    fetch('/api/feature-flags')
7      .then(res => res.json())
8      .then(data => {
9        setFlags(data);
10        setLoading(false);
11      })
12      .catch(() => setLoading(false));
13  }, []);
14
15  return { flags, loading };
16}
17
18// Usage with FeatureFlagProvider
19function App() {
20  const { flags, loading } = useRemoteFlags();
21
22  if (loading) return <Spinner />;
23
24  return (
25    <FeatureFlagProvider flags={flags}>
26      <Dashboard />
27    </FeatureFlagProvider>
28  );
29}

When to use Feature Flags?

| Situation | Flag type | |-----------|-----------| | New feature under development | Boolean flag (

true/false
) | | Testing different UI versions | A/B test (variants) | | Gradual deployment to users | Percentage rollout | | Quick disable of problematic feature | Kill switch | | Different features for different plans | User-based flag |

Feature Flags are like a central control panel on a spaceship. You can enable or disable any system with a single button, test a new engine on one ship before deploying it across the entire fleet, and immediately roll back changes when something goes wrong -- without having to land at a station and replace parts!

Go to CodeWorlds