@teracrafts/flagkit-react
v1.1.0
Published
FlagKit React SDK - Feature flags for React applications
Maintainers
Readme
@teracrafts/flagkit-react
React SDK for FlagKit feature flags. Provides React-specific hooks and components for easy integration.
Requirements
- React 17.0.0 or higher
@teracrafts/flagkit(peer dependency)
Installation
npm install @teracrafts/flagkit-react @teracrafts/flagkit
# or
yarn add @teracrafts/flagkit-react @teracrafts/flagkit
# or
pnpm add @teracrafts/flagkit-react @teracrafts/flagkitQuick Start
1. Wrap your app with FlagKitProvider
import { FlagKitProvider } from '@teracrafts/flagkit-react';
function App() {
return (
<FlagKitProvider
apiKey="sdk_your_api_key"
options={{ pollingInterval: 30000 }}
context={{ userId: 'user-123' }}
onReady={() => console.log('FlagKit ready!')}
onError={(err) => console.error('FlagKit error:', err)}
>
<YourApp />
</FlagKitProvider>
);
}2. Use hooks to access flags
import { useBooleanFlag, useStringFlag, useJsonFlag } from '@teracrafts/flagkit-react';
function MyComponent() {
// Simple boolean flag
const darkMode = useBooleanFlag('dark-mode', false);
// String flag
const theme = useStringFlag('theme', 'default');
// JSON flag with type safety
const config = useJsonFlag<{ maxItems: number }>('feature-config', { maxItems: 10 });
return (
<div className={darkMode ? 'dark' : 'light'}>
<h1>Theme: {theme}</h1>
<p>Max items: {config.maxItems}</p>
</div>
);
}API Reference
Components
<FlagKitProvider>
Provider component that initializes the SDK and makes it available to child components.
<FlagKitProvider
apiKey="sdk_xxx" // Required: Your API key
options={{ // Optional: SDK options
pollingInterval: 30000,
cacheEnabled: true,
}}
context={{ // Optional: Initial evaluation context
userId: 'user-123',
email: '[email protected]',
}}
onReady={() => {}} // Optional: Called when SDK is ready
onError={(err) => {}} // Optional: Called on errors
onFlagsChanged={(flags) => {}} // Optional: Called when flags update
>
<App />
</FlagKitProvider><FeatureFlag>
Component for conditional rendering based on flag value.
// Simple usage
<FeatureFlag flag="new-feature" fallback={<OldFeature />}>
<NewFeature />
</FeatureFlag>
// With loading state
<FeatureFlag
flag="feature"
loading={<Spinner />}
fallback={<Disabled />}
>
<Enabled />
</FeatureFlag>
// With render prop for value access
<FeatureFlag flag="ab-test">
{(value, result) => (
value === 'variant-a' ? <VariantA /> : <VariantB />
)}
</FeatureFlag>Hooks
useFlagKit()
Access the FlagKit client instance directly.
const client = useFlagKit();
// Track events
client?.track('button_clicked', { button: 'signup' });
// Identify users
client?.identify('user-456', { plan: 'premium' });useFlagKitStatus()
Get SDK initialization status.
const { isReady, isLoading, error, lastUpdated } = useFlagKitStatus();
if (isLoading) return <Spinner />;
if (error) return <Error message={error.message} />;useBooleanFlag(key, defaultValue, options?)
Get a boolean flag value.
const darkMode = useBooleanFlag('dark-mode', false);
const betaEnabled = useBooleanFlag('beta-features', false, {
context: { userId: 'special-user' }
});useStringFlag(key, defaultValue, options?)
Get a string flag value.
const theme = useStringFlag('theme', 'light');
const welcomeMessage = useStringFlag('welcome-message', 'Hello!');useNumberFlag(key, defaultValue, options?)
Get a number flag value.
const maxItems = useNumberFlag('max-items', 10);
const discountPercent = useNumberFlag('discount', 0);useJsonFlag<T>(key, defaultValue, options?)
Get a JSON flag value with type safety.
interface Config {
enabled: boolean;
maxRetries: number;
endpoints: string[];
}
const config = useJsonFlag<Config>('feature-config', {
enabled: false,
maxRetries: 3,
endpoints: [],
});useFlag(key, options?)
Get full flag evaluation result.
const { value, enabled, reason, isLoading, result } = useFlag('my-flag');
console.log(`Flag evaluated with reason: ${reason}`);useFlags(keys, options?)
Get multiple flags at once.
const flags = useFlags(['feature-a', 'feature-b', 'feature-c']);
return (
<div>
{flags['feature-a']?.enabled && <FeatureA />}
{flags['feature-b']?.enabled && <FeatureB />}
</div>
);useAllFlagsEnabled(keys, options?)
Check if all specified flags are enabled.
const allEnabled = useAllFlagsEnabled(['feature-a', 'feature-b']);
if (allEnabled) {
return <CombinedFeature />;
}useAnyFlagEnabled(keys, options?)
Check if any of the specified flags are enabled.
const anyEnabled = useAnyFlagEnabled(['beta-a', 'beta-b', 'beta-c']);
if (anyEnabled) {
return <BetaFeatures />;
}Advanced Usage
Direct Context Access
For advanced use cases, you can access the FlagKit context directly:
import { useFlagKitContext } from '@teracrafts/flagkit-react';
function AdvancedComponent() {
const { client, isReady, isLoading, error, lastUpdated } = useFlagKitContext();
if (!isReady) return null;
// Direct client access for complex operations
const result = client?.evaluate('complex-flag');
return <div>Last updated: {lastUpdated}</div>;
}Dynamic Context Updates
Update evaluation context dynamically based on user actions:
function UserProfile({ user }) {
const client = useFlagKit();
useEffect(() => {
if (client && user) {
client.identify(user.id, {
email: user.email,
plan: user.plan,
createdAt: user.createdAt,
});
}
}, [client, user]);
// Flags will re-evaluate with new context
const premiumFeature = useBooleanFlag('premium-feature', false);
return premiumFeature ? <PremiumUI /> : <StandardUI />;
}Type-Safe JSON Flags
When using useJsonFlag, provide a type parameter for full type safety:
interface FeatureConfig {
enabled: boolean;
maxRetries: number;
endpoints: string[];
}
// TypeScript will enforce the structure
const config = useJsonFlag<FeatureConfig>('feature-config', {
enabled: false,
maxRetries: 3,
endpoints: [],
});
// config.maxRetries is typed as number
// config.unknownProp would be a type errorServer-Side Rendering (SSR)
The SDK supports SSR by checking for the browser environment. During SSR, hooks will return default values until the client-side hydration completes.
// Safe to use in Next.js, Remix, etc.
const darkMode = useBooleanFlag('dark-mode', false);
// Returns false during SSR, then updates on clientNext.js App Router
For Next.js App Router, wrap the provider in a client component:
// providers.tsx
'use client';
import { FlagKitProvider } from '@teracrafts/flagkit-react';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<FlagKitProvider apiKey={process.env.NEXT_PUBLIC_FLAGKIT_KEY!}>
{children}
</FlagKitProvider>
);
}
// layout.tsx
import { Providers } from './providers';
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Developer Tools
FlagKit React includes DevTools components for inspecting and overriding feature flags during development. DevTools render inside Shadow DOM for complete style isolation — they will never affect your application's layout or CSS. When overrides are toggled in DevTools, all flag hooks automatically re-evaluate.
Quick Start
import { FlagKitProvider, FlagKitDevTools, useFlagKitContext } from '@teracrafts/flagkit-react';
function App() {
return (
<FlagKitProvider apiKey="sdk_your_key">
<MyApp />
{import.meta.env.DEV && <DevToolsMount />}
</FlagKitProvider>
);
}
function DevToolsMount() {
const { client } = useFlagKitContext();
if (!client) return null;
return <FlagKitDevTools client={client} theme="system" defaultOpen={false} />;
}Components
<FlagKitDevTools />
Floating overlay with a draggable toggle button. Mounts to document.body and manages its own lifecycle.
<FlagKitDevTools
client={client}
theme="system"
buttonPosition="bottom-right"
panelPosition="right"
enabled={import.meta.env.DEV}
/><FlagKitDevToolsPanel />
Embedded panel without floating toggle — renders inline in your layout. Useful for admin dashboards.
<FlagKitDevToolsPanel client={client} theme="dark" /><FlagKitDevToolsLazy />
Code-split variant using React.lazy that only downloads DevTools code on first render.
import { FlagKitDevToolsLazy } from '@teracrafts/flagkit-react';
{import.meta.env.DEV && <FlagKitDevToolsLazy client={client} />}useFlagKitDevTools Hook
Programmatic control over DevTools from any component inside the provider.
import { useFlagKitDevTools } from '@teracrafts/flagkit-react';
function DevToolsController() {
const { isOpen, open, close, toggle, overrideCount, devtools } = useFlagKitDevTools({
theme: 'system',
});
return (
<button onClick={toggle}>
DevTools {isOpen ? '(open)' : '(closed)'} — {overrideCount} overrides
</button>
);
}| Return | Type | Description |
|--------|------|-------------|
| isOpen | boolean | Whether the panel is currently open |
| open | () => void | Open the DevTools panel |
| close | () => void | Close the DevTools panel |
| toggle | () => void | Toggle the panel open/closed |
| overrideCount | number | Number of active flag overrides |
| devtools | FlagKitDevToolsCore \| null | Direct access to the core instance |
Configuration
All components accept the same configuration props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| client | FlagKitClient | required | Client instance from useFlagKitContext() |
| enabled | boolean | true | Whether to render |
| theme | 'light' \| 'dark' \| 'system' | 'system' | Color theme (follows OS preference in system mode) |
| buttonPosition | 'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right' | 'bottom-right' | Initial toggle button position |
| panelPosition | 'top' \| 'bottom' \| 'left' \| 'right' | 'right' | Which viewport edge the panel anchors to |
| initialSize | number | 420 | Initial panel size in pixels |
| minSize | number | 280 | Minimum panel size in pixels |
| maxSize | number | 800 | Maximum panel size in pixels |
| storagePrefix | string | 'flagkit' | localStorage key prefix |
| defaultOpen | boolean | false | Start with panel open |
| dashboardUrl | string | — | URL for "View in Dashboard" links |
| allowUrlOverrides | boolean | true (dev) / false (prod) | Allow URL-based override sharing |
| autoCleanStaleOverrides | boolean | false | Remove overrides for deleted flags on init |
| styleNonce | string | — | CSP nonce for injected <style> elements |
| shortcuts | boolean | true | Enable keyboard shortcuts |
| logErrors | boolean | true | Log DevTools internal errors to console |
Panels
DevTools includes six panels, switchable via tabs or number keys 1–6:
Flags Panel
Two-column master/detail layout for inspecting and overriding flags.
- Search — Filter flags by key name
- Filters — All, Starred, Boolean, String, Number, JSON, Overridden, Enabled, Disabled
- Sort — Key A-Z, Key Z-A, Updated, Type
- Star flags — Pin frequently used flags to the top
- Quick toggle — Inline boolean switch for fast toggling
- Override with expiry — Set overrides that auto-expire after a duration
- Flag detail — Full evaluation result, metadata, history, dependencies, and bug report generator
Context Panel
View and modify the evaluation context used for flag targeting.
- JSON tree viewer — Collapsible view of the current context
- Edit context — JSON editor with validation
- Personas — Save, load, and manage named context configurations
- PII detection — Warns when context contains fields that look like sensitive data
Events Panel
Timeline of SDK events with filtering and export.
- Event types — Flag Evaluated, Flags Changed, Custom, Identify, Override Set/Cleared, SDK Error, Init Complete
- Filter — By event type or free-text search
- Pause/Resume — Freeze the timeline while inspecting
- Export — Copy all events to clipboard as JSON
Network Panel
Monitor all HTTP requests made by the SDK.
- Request list — Method, URL path, status code, duration, response size
- Status indicators — Green (2xx), yellow (3xx), red (4xx/5xx)
- Retry tracking — Retried requests show a retry icon and link to the original
- Request detail — Full URL, method, status, duration, size, timestamp
Status Panel
SDK health dashboard showing runtime configuration and state.
- SDK Info — SDK version, DevTools version, session ID, ready state
- Configuration — Polling interval, cache TTL, timeout, retries, offline mode
- Cache — Flag count, cache status, persistence, stale/offline badges
- Security — Strict PII mode, evaluation jitter, error sanitization status
Settings Panel
DevTools preferences and override management.
- Theme selector — Light, Dark, or System (persisted across sessions)
- Override presets — Save named sets of overrides and apply them later
- Import/Export — Copy overrides to clipboard as JSON, or paste to import
- Clear all — Remove all active overrides at once
Override Engine
When you toggle a flag in DevTools, the override engine intercepts flag evaluations and returns your override value instead of the server value. Overrides persist to localStorage and sync across browser tabs automatically.
All React flag hooks (useFlag, useBooleanFlag, useStringFlag, useNumberFlag, useJsonFlag) re-evaluate automatically when overrides change.
URL Override Sharing
Share flag overrides with teammates via URL. Overrides are encoded in the __fk_overrides query parameter and automatically applied when the page loads.
Command Palette
Press Cmd+K (Mac) or Ctrl+K (Windows/Linux) when DevTools is open to launch the command palette. Search across tabs, commands, and flags.
Keyboard Shortcuts
| Shortcut | Action |
|----------|--------|
| Ctrl/Cmd + Shift + F | Toggle DevTools panel |
| Escape | Close DevTools panel |
| Ctrl/Cmd + K | Open/close command palette |
| 1 – 6 | Switch panels (Flags, Context, Events, Network, Status, Settings) |
Console API
When mounted, DevTools registers window.__FLAGKIT_DEVTOOLS__ for use in the browser console:
__FLAGKIT_DEVTOOLS__.getFlag('my-flag') // Full evaluation result
__FLAGKIT_DEVTOOLS__.setOverride('my-flag', true, 'boolean')
__FLAGKIT_DEVTOOLS__.clearOverride('my-flag')
__FLAGKIT_DEVTOOLS__.clearAllOverrides()
__FLAGKIT_DEVTOOLS__.getAllOverrides()
__FLAGKIT_DEVTOOLS__.open()
__FLAGKIT_DEVTOOLS__.close()
__FLAGKIT_DEVTOOLS__.toggle()
__FLAGKIT_DEVTOOLS__.getStatus() // { isOpen, mounted, overrideCount, activeTab, theme }Production Safety
DevTools code should be excluded from production bundles. Use conditional rendering:
function App() {
return (
<FlagKitProvider apiKey="sdk_your_key">
<MyApp />
{import.meta.env.DEV && <DevToolsMount />}
</FlagKitProvider>
);
}The <FlagKitDevToolsLazy /> variant ensures DevTools code is only downloaded when rendered, keeping your production bundle clean when combined with conditional rendering.
Security
The React SDK inherits all security features from the core TypeScript SDK.
Security Options
Configure security options through the options prop on FlagKitProvider:
<FlagKitProvider
apiKey="sdk_your_api_key"
options={{
// Throws SecurityError instead of warning when PII detected
strictPIIMode: true,
// Secondary key for seamless key rotation
secondaryApiKey: "sdk_backup_key",
// Grace period for key rotation (default: 5 minutes)
keyRotationGracePeriod: 300000,
// Local development port (uses http://localhost:{port}/api/v1)
// ONLY for development - do not use in production
}}
>
<App />
</FlagKitProvider>API Key Types
Use the appropriate key type for your environment:
sdk_- Client SDK keys, safe for browser usecli_- Limited client keys with restricted permissionssrv_- Server keys, should ONLY be used server-side
// Good: Client SDK key for React apps
<FlagKitProvider apiKey="sdk_your_client_key">
// Warning: Server keys in browser will trigger a security warning
<FlagKitProvider apiKey="srv_your_server_key">Strict PII Mode
Enable strict PII mode to prevent accidental PII exposure:
<FlagKitProvider
apiKey="sdk_xxx"
options={{ strictPIIMode: true }}
context={{
userId: "user-123",
// This will throw SecurityError instead of just warning
email: "[email protected]",
}}
>With strict mode enabled, potential PII fields in context will throw a SecurityError. Either:
- Remove the PII field from context
- Mark it as a private attribute (not sent to server)
Best Practices
1. Place Provider at the Root
Wrap your entire app with FlagKitProvider to ensure all components have access:
// Correct: Provider at root
<FlagKitProvider apiKey="sdk_xxx">
<App />
</FlagKitProvider>2. Use Appropriate Default Values
Always provide sensible defaults that work when flags are unavailable:
// Good: Safe default for a potentially breaking change
const newCheckout = useBooleanFlag('new-checkout', false);
// Good: Default to a working configuration
const config = useJsonFlag('api-config', {
timeout: 5000,
retries: 3,
});3. Handle Loading States
Use the loading prop or useFlagKitStatus to handle initialization:
function App() {
const { isLoading, error } = useFlagKitStatus();
if (error) return <ErrorBoundary error={error} />;
if (isLoading) return <AppSkeleton />;
return <MainContent />;
}4. Avoid Flag Evaluation in Loops
Memoize flag values when rendering lists:
// Good: Flag evaluated once
const showBadge = useBooleanFlag('show-badge', false);
return items.map(item => (
<Item key={item.id} showBadge={showBadge} />
));5. Track Important Conversions
Use the client to track events when users interact with flagged features:
const client = useFlagKit();
const newFeature = useBooleanFlag('new-feature', false);
const handleClick = () => {
client?.track('feature_used', {
feature: 'new-feature',
variant: newFeature ? 'enabled' : 'disabled',
});
};License
MIT
