All files / packages/hooks/src useLaunchDarkly.ts

100% Statements 39/39
100% Branches 19/19
100% Functions 9/9
100% Lines 39/39

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                                        30x 30x 30x     30x 30x     30x 30x   30x 16x   16x 17x 16x   17x 17x 17x       14x 12x     3x 2x 2x     17x 13x           16x       16x 2x 1x       16x   16x 16x 16x       30x     2x       4x             2x       4x             2x       4x             2x       4x                
import { useEffect, useRef, useState } from "react";
 
import { launchdarklyService } from "@repo/services/launchdarkly";
 
type UseLaunchDarklyResult<T> = {
  value: T;
  loading: boolean;
  error: Error | null;
};
 
type VariationMethod<T> = (flagKey: string, defaultValue: T) => Promise<T>;
 
/**
 * Generic hook for LaunchDarkly feature flags with real-time updates
 */
export function useLaunchDarkly<T>(
  flagKey: string,
  defaultValue: T,
  variationMethod: VariationMethod<T>,
): UseLaunchDarklyResult<T> {
  const [value, setValue] = useState<T>(defaultValue);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<Error | null>(null);
 
  // Store variation method in ref to avoid dependency issues
  const variationMethodRef = useRef(variationMethod);
  variationMethodRef.current = variationMethod;
 
  // Store defaultValue in ref to avoid stale closures
  const defaultValueRef = useRef(defaultValue);
  defaultValueRef.current = defaultValue;
 
  useEffect(() => {
    let isMounted = true;
 
    const updateFlagValue = async (setLoadingState: boolean) => {
      if (setLoadingState && isMounted) {
        setLoading(true);
      }
      try {
        setError(null);
        const flagValue = await variationMethodRef.current(
          flagKey,
          defaultValueRef.current,
        );
        if (isMounted) {
          setValue(flagValue);
        }
      } catch (err) {
        if (isMounted) {
          setError(err instanceof Error ? err : new Error(String(err)));
          setValue(defaultValueRef.current);
        }
      } finally {
        if (setLoadingState && isMounted) {
          setLoading(false);
        }
      }
    };
 
    // Fetch initial value
    updateFlagValue(true);
 
    // Subscribe to real-time flag changes
    // Use a stable handler function that always has access to latest state
    const handler = () => {
      if (isMounted) {
        updateFlagValue(false);
      }
    };
 
    const unsubscribe = launchdarklyService.onFlagChange(flagKey, handler);
 
    return () => {
      isMounted = false;
      unsubscribe();
    };
  }, [flagKey]);
 
  return { value, loading, error };
}
 
export const useLaunchDarklyBool = (
  flagKey: string,
  defaultValue: boolean = false,
): UseLaunchDarklyResult<boolean> => {
  return useLaunchDarkly(
    flagKey,
    defaultValue,
    launchdarklyService.boolVariation.bind(launchdarklyService),
  );
};
 
export const useLaunchDarklyString = (
  flagKey: string,
  defaultValue: string = "",
): UseLaunchDarklyResult<string> => {
  return useLaunchDarkly(
    flagKey,
    defaultValue,
    launchdarklyService.stringVariation.bind(launchdarklyService),
  );
};
 
export const useLaunchDarklyNumber = (
  flagKey: string,
  defaultValue: number = 0,
): UseLaunchDarklyResult<number> => {
  return useLaunchDarkly(
    flagKey,
    defaultValue,
    launchdarklyService.numberVariation.bind(launchdarklyService),
  );
};
 
export const useLaunchDarklyJson = <T = unknown>(
  flagKey: string,
  defaultValue: T,
): UseLaunchDarklyResult<T> => {
  return useLaunchDarkly(
    flagKey,
    defaultValue,
    launchdarklyService.jsonVariation.bind(
      launchdarklyService,
    ) as VariationMethod<T>,
  );
};