Installation
npm install @omniretail/omniflags-react
Peer dependencies: React 18+
Setup
Wrap your app root with OmniFlagsProvider. It fetches the flag snapshot and starts background polling.
Vite / CRA
Next.js (App Router)
Next.js (Pages Router)
// main.tsx
import { createRoot } from 'react-dom/client';
import { OmniFlagsProvider } from '@omniretail/omniflags-react';
import App from './App';
createRoot(document.getElementById('root')!).render(
<OmniFlagsProvider sdkKey="pk_live_...">
<App />
</OmniFlagsProvider>
);
OmniFlagsProvider is a client component. Wrap it to use in your root layout:// components/omniflags-provider.tsx
'use client';
import { OmniFlagsProvider } from '@omniretail/omniflags-react';
export function OmniFlagsClientProvider({ children }: { children: React.ReactNode }) {
return (
<OmniFlagsProvider sdkKey={process.env.NEXT_PUBLIC_OMNIFLAGS_SDK_KEY!}>
{children}
</OmniFlagsProvider>
);
}
// app/layout.tsx
import { OmniFlagsClientProvider } from '@/components/omniflags-provider';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<OmniFlagsClientProvider>
{children}
</OmniFlagsClientProvider>
</body>
</html>
);
}
Any component using flag hooks must be a Client Component ('use client').// pages/_app.tsx
import type { AppProps } from 'next/app';
import { OmniFlagsProvider } from '@omniretail/omniflags-react';
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<OmniFlagsProvider sdkKey={process.env.NEXT_PUBLIC_OMNIFLAGS_SDK_KEY!}>
<Component {...pageProps} />
</OmniFlagsProvider>
);
}
Loading state
Hooks return defaultValue until the first snapshot arrives. If you need to gate rendering on that:
import { useFlagStatus } from '@omniretail/omniflags-react';
function AppShell() {
const { isLoading } = useFlagStatus();
if (isLoading) return <FullScreenSpinner />;
return <App />;
}
Most apps don’t need this — hooks update in place once the snapshot arrives.
Hooks
useFlag
Boolean flag evaluation. Re-renders only when this flag’s value changes.
import { useFlag } from '@omniretail/omniflags-react';
function PromotionalBanner({ customerId }: { customerId: string }) {
const showBanner = useFlag('product-listing.show-promo-products', { customerId });
if (!showBanner) return null;
return <Banner />;
}
| Parameter | Type | Default | Description |
|---|
flagKey | string | required | {projectKey}.{flagKey} |
context | EvaluationContext | {} | Targeting attributes |
defaultValue | boolean | false | Fallback when flag is missing, wrong type, or client not ready |
useFlagValue
Returns the typed value of any flag — string, number, or object.
import { useFlagValue } from '@omniretail/omniflags-react';
function PromoBanner({ customerId }: { customerId: string }) {
const colour = useFlagValue('product-listing.promo-banner-colour', 'blue', { customerId });
return <div style={{ backgroundColor: colour }}>Limited time offer</div>;
}
const maxCartItems = useFlagValue('cart.max-items', 20, { customerId });
const config = useFlagValue<CheckoutConfig>('checkout.config', defaultConfig, { customerId });
| Parameter | Type | Required | Description |
|---|
flagKey | string | yes | {projectKey}.{flagKey} |
defaultValue | T | yes | Fallback value |
context | EvaluationContext | no | Targeting attributes |
useFlagVariant
Returns the full EvaluationResult — variant key, reason, and rule ID. Useful when you need to log evaluation details or branch on variant names directly.
import { useFlagVariant } from '@omniretail/omniflags-react';
function BannerWithAnalytics({ customerId }: { customerId: string }) {
const result = useFlagVariant('product-listing.promo-banner-colour', { customerId });
useEffect(() => {
analytics.track('flag_evaluated', {
flag: 'product-listing.promo-banner-colour',
variant: result.variant,
reason: result.reason,
ruleId: result.ruleId,
});
}, [result.variant, result.reason]);
return <Banner colour={result.value as string} />;
}
| Field | Type | Description |
|---|
value | unknown | Resolved flag value |
variant | string | null | Matched variant key |
reason | EvaluationReason | See reason codes |
ruleId | string | null | Matched rule ID, if any |
errorCode | ErrorCode | null | Set when reason is ERROR |
useFlagStatus
Current state of the flag client.
import { useFlagStatus } from '@omniretail/omniflags-react';
function DiagnosticBadge() {
const { isLoading, isFetching, origin, error } = useFlagStatus();
if (error) console.error('OmniFlags fetch failed:', error);
return <span title={`Flags: ${origin}`}>{isFetching ? '⟳' : '✓'}</span>;
}
| Field | Type | Description |
|---|
isLoading | boolean | true until the first snapshot loads (cache or network) |
isFetching | boolean | true while a network request is in flight |
origin | 'NONE' | 'CACHE' | 'SERVER' | Source of the active snapshot |
error | Error | null | Last fetch error |
useSetFlagContext
Sets a global evaluation context for the whole provider tree. Individual hook calls that pass their own context will be merged with it — per-call attributes win on conflict.
import { useSetFlagContext } from '@omniretail/omniflags-react';
function AuthenticatedApp({ user }: { user: CurrentUser }) {
useSetFlagContext({
customerId: user.id,
country: user.country,
platform: 'web',
});
return <App />;
}
Clears on unmount.
| Parameter | Type | Description |
|---|
context | EvaluationContext | undefined | Pass undefined to clear explicitly. |
Evaluation context
Attributes used to match targeting rules and assign rollout buckets. Pass per-hook or once globally via useSetFlagContext.
useFlag('checkout.charge-delivery-fee', {
customerId: '4821',
country: 'Nigeria',
city: 'Lagos',
platform: 'web',
appVersion: '4.1.0',
});
| Attribute | Type | Notes |
|---|
customerId | string | number | Primary bucketing key |
agentId | string | number | Used when customerId is absent |
businessId | string | number | |
businessBranchId | string | number | |
country | string | e.g. "Nigeria", "Ghana" |
city | string | |
platform | string | web, ios, android |
appVersion | string | Semver string |
Any extra key-value pairs are forwarded to targeting rules as custom attributes.
Rollout and traffic splits require a bucketing key (customerId, agentId, etc.). Without one the SDK returns the flag’s default value with MISSING_TARGETING_KEY.