@core-sdk/feature-flags-react
v0.4.0
Published
Minimal React context + hooks for @core-sdk/feature-flags-sdk
Readme
@core-sdk/feature-flags-react
React bindings on top of @core-sdk/feature-flags-sdk (pulled in automatically): a single FeatureFlagsClient in context, hooks for flag state, and an optional identify helper.
Install
npm install @core-sdk/feature-flags-react reactPeer: react (18+). You do not need to add @core-sdk/feature-flags-sdk to your app; install it only if you want a direct dependency (e.g. advanced usage or version pinning).
Usage
Wrap your tree with FeatureFlagsProvider and pass a shared FeatureFlagsClient instance.
"use client";
import {
FeatureFlagsClient,
FeatureFlagsProvider,
useFeatureFlagEnabled,
} from "@core-sdk/feature-flags-react";
const client = new FeatureFlagsClient({
baseUrl: "https://api.example.com",
projectKey: "my-project",
});
export function App() {
return (
<FeatureFlagsProvider client={client} autoBootstrap>
<SidebarGate />
</FeatureFlagsProvider>
);
}
function SidebarGate() {
const { enabled, loading } = useFeatureFlagEnabled("sidebar");
if (loading) return null;
return enabled ? <Sidebar /> : null;
}FeatureFlagsProvider
client:FeatureFlagsClient(required).autoBootstrap: whentrue, runsclient.bootstrap()once after mount and keepschildrenunmounted until bootstrap finishes (or the gate times out). That way hooks underchildrendo not race ahead of the cache and trigger per-flag detail fetches. Omit if you bootstrap elsewhere or usehydrateFromRecordson the SDK (see Next.js below).bootstrapGateTimeoutMs(default10000): maximum wait beforechildrenare shown anyway, so a stuck or slow network never blocks the whole app. When the timeout fires, aconsole.warnis emitted. If set to0or less, no timeout is scheduled; the gate opens when bootstrap resolves or rejects (a permanently pending bootstrap could still block—avoid hungfetchin that mode).bootstrappingFallback: optional UI (e.g. spinner) rendered instead ofchildrenwhile the gate is closed. Defaults tonull.
Bootstrap failures are logged to console.error; the gate still opens so the UI is not stuck.
Hooks
| Hook | Purpose |
|------|---------|
| useFeatureFlagsClient() | Returns the context client; throws if used outside the provider. |
| useFeatureFlagEnabled(flagKey, context?) | Resolves { enabled, loading }. In server evaluation mode it calls peekServerEnabled first so a prior prefetchServerEvaluations avoids any HTTP; otherwise it uses isEnabled (which batches concurrent evaluations into one POST .../evaluate). Optional context is merged with SDK identify traits. |
| useFeatureFlagActive | Alias of useFeatureFlagEnabled. |
| useIdentify() | Returns { identify(distinctId, traits?), reset } bound to the context client. |
identify and refreshes
The SDK merges identify traits into evaluation context. The hooks do not subscribe to identity changes; after identify (or when definitions change), call client.invalidate() or remount consumers so useFeatureFlagEnabled re-runs.
Server mode: batch evaluate and prefetch
With evaluationMode: "server", the SDK posts to POST /projects/:projectKey/flags/evaluate. Concurrent isEnabled / hook resolutions for the same evaluation context are merged into one request per microtask. Use client.prefetchServerEvaluations(flagKeys, extraContext?) after identify() (e.g. in your profile gate) so most hooks only read peekServerEnabled and skip the network entirely. Unknown keys still work via the batched isEnabled path.
Evaluation context and re-renders
Omitting the second argument uses a stable empty object so the hook does not loop on every render. If you pass an inline object (e.g. useFeatureFlagEnabled("x", { orgId })), keep it referentially stable when values are unchanged (e.g. useMemo), or the effect will re-fetch.
Debugging
- “must be used within FeatureFlagsProvider”: import hooks only under a client tree wrapped by
FeatureFlagsProvider. - Flag always disabled / loading stuck: ensure
bootstrap()orhydrateFromRecordshas populated the cache for that key; check network andfetchImplin non-browser environments.
Next.js App Router
Hooks and FeatureFlagsProvider must live in a Client Component ("use client"). In a Server Component (e.g. app/layout.tsx), call await new FeatureFlagsClient(config).bootstrap(), pass the returned records into a client wrapper, then new FeatureFlagsClient(browserConfig) + client.hydrateFromRecords(records) and FeatureFlagsProvider with autoBootstrap={false} so the browser does not fetch twice. Use getHeaders / cookies on the server client if your API keys requests on them; mirror the same headers on the browser client when needed.
