OpenFeature React: Audit and Migrate Your LaunchDarkly React App
OpenFeature React: Audit and Migrate Your LaunchDarkly React App
Section titled “OpenFeature React: Audit and Migrate Your LaunchDarkly React App”Your React app is probably riddled with ldClient.variation() calls and useFlags() hooks spread across 30–50 components. They work, they’ve always worked, and nobody remembers which flags are still live. Then LaunchDarkly’s bill arrives — or your platform team announces an OpenFeature mandate — and you realise you have no idea how many flag keys exist across your component tree.
That’s the core problem with LaunchDarkly SDK usage in long-lived React codebases: the calls accumulate invisibly. You can’t migrate what you haven’t measured.
This guide walks through auditing a React project with FlagLint, reading the results, and doing the actual LaunchDarkly SDK → OpenFeature React swap, component by component.
Why flag debt builds up faster in React
Section titled “Why flag debt builds up faster in React”In a Node.js service, flag evaluations tend to live close together — middleware, handlers, config loaders. In React, they scatter. A flag that controls a header renders in AppShell.tsx. The same flag shows up in MobileNav.tsx, DesktopNav.tsx, and NavAnalytics.tsx. One flag key, four call sites, none of them talking to each other.
Three years in, you have 80 flags in LaunchDarkly, 140 call sites in React, and no map. Flags that shipped are still evaluated on every render. That’s flag debt — stale signals running up cost and making every component harder to understand.
The migration plan cannot start with “pick a component and swap the hook.” It has to start with: what flag keys exist, what call type each uses, which ones have a high staleness signal, and what the current readiness score is. FlagLint produces that map.
Step 1: Run the FlagLint audit
Section titled “Step 1: Run the FlagLint audit”FlagLint targets TypeScript and JavaScript source files. Running it against a directory that contains only documentation shows the baseline case:
npx flaglint@latest audit ./src/content/docs/Output:
- Auditing ./src/content/docs/...No matching files found. Check your .flaglintrc include patterns.Correct — that directory holds only Markdown. For a React project, point the audit at src/:
npx flaglint@latest audit ./src/FlagLint walks every .ts and .tsx file, extracts every flag key, classifies each call type, assigns staleness signals, and prints the overall readiness score.
Step 2: Configure .flaglintrc for React
Section titled “Step 2: Configure .flaglintrc for React”Out of the box, FlagLint looks for calls from launchdarkly-node-server-sdk. To catch React-specific patterns — the useFlags() hook, the withLDConsumer() HOC, and the useLDClient() hook — add a .flaglintrc at the repo root:
{ "include": ["src/**/*.ts", "src/**/*.tsx"], "exclude": ["src/**/*.test.tsx", "src/**/*.spec.ts"], "sdks": ["launchdarkly-js-client-sdk", "launchdarkly-react-client-sdk"]}The launchdarkly-react-client-sdk entry tells FlagLint to recognise the React SDK’s call types. Without it, hook-based flag evaluations are invisible to the audit and won’t appear in the flag debt count or the readiness score.
Re-run after saving .flaglintrc and you’ll see each flag key, its call type, and whether the staleness signal is high, medium, or low.
Step 3: Understand the readiness score
Section titled “Step 3: Understand the readiness score”The readiness score runs from 0 to 100. Zero means every flag evaluation in your codebase goes through the LaunchDarkly SDK. One hundred means every evaluation goes through an OpenFeature provider.
FlagLint computes it by comparing LaunchDarkly SDK call sites to total flag call sites. A repo at score 40 has moved 40% of its flag evaluations to OpenFeature. The remaining 60% still carry LaunchDarkly SDK imports.
The readiness score tells you at a glance how far through the migration plan you are, and gives you a concrete number to track PR-by-PR. See LaunchDarkly Flag Debt: Audit, Estimate, and Prioritize Your Migration for a deeper breakdown of how to read the score alongside flag debt estimates.
Step 4: Before and after — LaunchDarkly SDK to OpenFeature React
Section titled “Step 4: Before and after — LaunchDarkly SDK to OpenFeature React”Hooks (the common case)
Section titled “Hooks (the common case)”Before — LaunchDarkly React SDK:
import { useFlags } from 'launchdarkly-react-client-sdk';
function BillingPage() { const { billingV2Rollout } = useFlags(); return billingV2Rollout ? <BillingV2 /> : <BillingV1 />;}After — OpenFeature React:
import { useBooleanFlagValue } from '@openfeature/react-sdk';
function BillingPage() { const billingV2Rollout = useBooleanFlagValue('billing-v2-rollout', false); return billingV2Rollout ? <BillingV2 /> : <BillingV1 />;}Two things change. First, the import: launchdarkly-react-client-sdk → @openfeature/react-sdk. Second, the call type: useFlags() returns all flags at once as a camelCase object, so you destructure billingV2Rollout. The OpenFeature useBooleanFlagValue() evaluates a single flag key and takes the raw kebab-case key 'billing-v2-rollout' as a string.
FlagLint’s audit marks the camelCase-to-kebab-case normalisation as a staleness signal to watch for. If a flag key was committed as billingV2Rollout in one component and billing-v2-rollout in another, the audit surfaces both call sites so you can consolidate before migrating.
HOC pattern
Section titled “HOC pattern”Older React codebases use withLDConsumer instead of hooks:
Before:
import { withLDConsumer } from 'launchdarkly-react-client-sdk';
function FeatureNav({ flags }) { return flags.showNewNav ? <NewNav /> : <OldNav />;}
export default withLDConsumer()(FeatureNav);After:
import { useBooleanFlagValue } from '@openfeature/react-sdk';
function FeatureNav() { const showNewNav = useBooleanFlagValue('show-new-nav', false); return showNewNav ? <NewNav /> : <OldNav />;}
export default FeatureNav;The HOC disappears entirely. OpenFeature React hooks pull from context without a wrapper. FlagLint classifies withLDConsumer wraps as their own call type during the audit, so the migration plan counts them separately from hook-based evaluations.
Wiring the OpenFeature provider at app root
Section titled “Wiring the OpenFeature provider at app root”Before any hook resolves, set the OpenFeature provider once at the top of your tree. The LaunchDarkly OpenFeature provider is the easiest starting point — it keeps your existing flags working during migration:
import { OpenFeatureProvider, OpenFeature } from '@openfeature/react-sdk';import { LaunchDarklyClientProvider } from '@launchdarkly/openfeature-js-client-sdk';
const ldClient = LaunchDarklyClientProvider.createClient('your-client-side-id');OpenFeature.setProvider(new LaunchDarklyClientProvider(ldClient));
function App() { return ( <OpenFeatureProvider> <Router /> </OpenFeatureProvider> );}Once this is in place, every component that calls useBooleanFlagValue() evaluates against the LaunchDarkly OpenFeature provider automatically. You can swap the provider to Flipt, Unleash, or any other OpenFeature-compatible system later without touching component code. That’s the architectural value: the migration plan stops being locked to one vendor.
Step 5: Clear stale flags before migrating
Section titled “Step 5: Clear stale flags before migrating”Not every flag needs an OpenFeature equivalent. Some flags are done — the rollout shipped, the old branch is deleted, but the LaunchDarkly SDK call is still in the component tree because removing it never made the sprint.
FlagLint assigns a staleness signal based on source history and call frequency. A flag key with a high staleness signal, a single call site, and a boolean call type is a strong candidate for outright deletion: remove the LaunchDarkly SDK call, remove the conditional branch, archive the flag in LaunchDarkly. That drops flag debt and improves the readiness score without writing any OpenFeature code.
Kill high-staleness-signal flags first. Then migrate the rest. The LaunchDarkly Feature Flag Cleanup guide covers the removal workflow in detail for TypeScript codebases.
Step 6: Enforce in CI
Section titled “Step 6: Enforce in CI”Once you start migrating, new LaunchDarkly SDK calls introduced in PRs will quietly undo your progress. FlagLint has a CI mode that fails the build when the readiness score drops below a threshold you set. Wire it into your pipeline so that every PR that adds a new LaunchDarkly SDK call type is blocked until the author migrates it to an OpenFeature React hook instead. See Enforcing Your LaunchDarkly to OpenFeature Migration in GitHub Actions for the exact workflow and YAML.
Next steps
Section titled “Next steps”- Run
npx flaglint@latest audit ./src/against your actual React source. Get the real flag key count and readiness score before estimating effort. - Add
.flaglintrcwithlaunchdarkly-react-client-sdkin thesdkslist so hooks and HOCs appear in the flag debt count. - Delete high-staleness-signal flags first. Reducing the LaunchDarkly SDK call count before migrating keeps each PR focused.
- Wire the OpenFeature provider at app root. Flags keep working via the LaunchDarkly OpenFeature provider while you migrate component by component.
- Track the readiness score per PR. FlagLint in CI gives you a number that goes up. When it hits 100, the LaunchDarkly SDK import can come out of
package.json.
OpenFeature React is a hook-for-hook replacement at the component level. The migration plan is mechanical once you have the audit. FlagLint gives you that map.