All files / packages/hooks/src launchdarkly.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                                        38x 38x 38x     38x 38x     38x 38x   38x 20x   20x 21x 20x   21x 21x 21x       18x 16x     3x 2x 2x     21x 17x           20x       20x 2x 1x       20x   20x 20x 20x       38x     2x       4x             2x       4x             2x       4x             2x       12x                
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>,
  );
};