All files / web/src/hooks useFeatureFlag.ts

0% Statements 0/66
0% Branches 0/1
0% Functions 0/1
0% Lines 0/66

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                                                                                                                                     
'use client'

import { useMemo } from 'react'
import { useQuery } from '@tanstack/react-query'
import { featureFlagKeys } from '@/lib/queryKeys'

export interface FlagValue {
  enabled: boolean
  config: unknown
}

export type FlagsResponse = {
  flags: Record<string, FlagValue>
}

const EMPTY_FLAGS: Record<string, FlagValue> = {}

async function fetchFlags(): Promise<FlagsResponse> {
  const res = await fetch('/api/feature-flags')
  if (!res.ok) throw new Error('Failed to fetch feature flags')
  return res.json()
}

/**
 * Hook to get all feature flags (bulk fetch).
 * Uses React Query with 60s stale time.
 */
export function useFeatureFlags() {
  const { data, isLoading } = useQuery({
    queryKey: featureFlagKeys.all,
    queryFn: fetchFlags,
    staleTime: 60_000,
  })

  return {
    flags: data?.flags ?? EMPTY_FLAGS,
    isLoading,
  }
}

/**
 * Hook to get a single feature flag by key.
 *
 * Performance: shares the bulk flags query (single HTTP request for all flags).
 * Uses React Query `select` to extract one flag, and `useMemo` on the return
 * value so consumers only re-render when `enabled` or `config` actually change.
 */
export function useFeatureFlag(key: string) {
  const { data, isLoading } = useQuery({
    queryKey: featureFlagKeys.all,
    queryFn: fetchFlags,
    staleTime: 60_000,
    select: (data) => data.flags[key] ?? null,
    // React Query uses referential equality on select output by default.
    // structuralSharing deep-compares the selected value so a refetch that
    // returns identical data won't produce a new reference.
    structuralSharing: true,
  })

  const enabled = data?.enabled ?? false
  const config = data?.config ?? null

  // Stable reference — only changes when the primitive `enabled` or
  // the structurally-shared `config` actually differ.
  return useMemo(() => ({ enabled, config, isLoading }), [enabled, config, isLoading])
}