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                                        34x 34x 34x     34x 34x     34x 34x   34x 18x   18x 19x 18x   19x 19x 19x 16x 14x     3x 2x 2x     19x 15x           18x       18x 2x 1x       18x   18x 18x 18x       34x     2x       8x             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>,
  );
};