@natify/feature-flag-growthbook
v1.0.2
Published
Feature Flags adapter for Natify Framework using GrowthBook.
Readme
@natify/feature-flag-growthbook
Feature Flags adapter for Natify Framework using GrowthBook.
Installation
pnpm add @natify/feature-flag-growthbook @growthbook/growthbook-reactFor Real-Time Streaming (Optional)
If you want to enable real-time feature flag updates:
pnpm add react-native-sseiOS
cd ios && pod install && cd ..Android
No additional configuration required.
Configuration
Get GrowthBook Client Key
- Create an account on GrowthBook
- Create a new project
- Copy the Client Key from Settings → API Keys
Usage
Basic Configuration
import { NatifyProvider } from "@natify/core";
import { GrowthBookFeatureFlagAdapter } from "@natify/feature-flag-growthbook";
const featureFlags = new GrowthBookFeatureFlagAdapter({
clientKey: "YOUR_GROWTHBOOK_CLIENT_KEY",
});
const config = {
featureflags: featureFlags,
// ... other adapters
};
function App() {
return (
<NatifyProvider config={config}>
<MyApp />
</NatifyProvider>
);
}Advanced Configuration
const featureFlags = new GrowthBookFeatureFlagAdapter({
clientKey: "YOUR_GROWTHBOOK_CLIENT_KEY",
apiHost: "https://cdn.growthbook.io", // Optional
enableDevMode: true, // For development
enableStreaming: true, // Real-time updates
enableRemoteEval: false, // Remote evaluation (requires proxy server)
initialAttributes: {
id: currentUserId,
email: userEmail,
plan: "premium",
},
});
// Initialize
await featureFlags.init();With Streaming (Real-Time Updates)
import { setPolyfills } from "@growthbook/growthbook";
import EventSource from "react-native-sse";
// Configure SSE polyfill for React Native
setPolyfills({
EventSource: EventSource,
});
const featureFlags = new GrowthBookFeatureFlagAdapter({
clientKey: "YOUR_GROWTHBOOK_CLIENT_KEY",
enableStreaming: true,
});
await featureFlags.init();Check Feature Flags
Check If Enabled
import { useAdapter, FeatureFlagPort } from "@natify/core";
function PremiumFeature() {
const featureFlags = useAdapter<FeatureFlagPort>("featureflags");
const isPremiumEnabled = featureFlags.isEnabled("premium-feature");
if (isPremiumEnabled) {
return <PremiumComponent />;
}
return <StandardComponent />;
}Get Flag Value
function ThemeSelector() {
const featureFlags = useAdapter<FeatureFlagPort>("featureflags");
// Get theme from feature flag
const theme = featureFlags.getValue<string>("app-theme", "light");
return <ThemeProvider theme={theme}>...</ThemeProvider>;
}Get Complete Result
function FeatureBanner() {
const featureFlags = useAdapter<FeatureFlagPort>("featureflags");
const result = featureFlags.getFeatureFlag<{ message: string; color: string }>(
"welcome-banner"
);
if (result.enabled && result.value) {
return (
<Banner color={result.value.color}>
{result.value.message}
</Banner>
);
}
return null;
}Get Multiple Flags
function FeatureGate() {
const featureFlags = useAdapter<FeatureFlagPort>("featureflags");
const flags = featureFlags.getFeatureFlags([
"premium-feature",
"new-checkout",
"dark-mode",
]);
return (
<View>
{flags["premium-feature"].enabled && <PremiumButton />}
{flags["new-checkout"].enabled && <NewCheckoutFlow />}
{flags["dark-mode"].enabled && <DarkModeToggle />}
</View>
);
}User Attributes
Set Attributes (Identify User)
import { useAdapter, FeatureFlagPort } from "@natify/core";
function useAuth() {
const featureFlags = useAdapter<FeatureFlagPort>("featureflags");
const login = async (user: User) => {
// Set user attributes for targeting
featureFlags.setAttributes({
id: user.id,
email: user.email,
name: user.name,
plan: user.plan,
roles: user.roles,
country: user.country,
});
};
const logout = async () => {
// Clear attributes
featureFlags.clearAttributes();
};
return { login, logout };
}Update Attributes
// When user updates their plan
featureFlags.setAttributes({
...featureFlags.getAttributes(),
plan: "premium",
});
// Refresh flags to apply new targeting rules
await featureFlags.refresh();Common Use Cases
Simple Feature Gate
function NewFeatureScreen() {
const featureFlags = useAdapter<FeatureFlagPort>("featureflags");
if (!featureFlags.isEnabled("new-feature")) {
return <ComingSoonScreen />;
}
return <NewFeatureContent />;
}UI Variants
function CheckoutButton() {
const featureFlags = useAdapter<FeatureFlagPort>("featureflags");
// Get button variant
const buttonVariant = featureFlags.getValue<string>("checkout-button-variant", "default");
return (
<Button variant={buttonVariant} onPress={handleCheckout}>
Checkout
</Button>
);
}Dynamic Configuration
function AppConfig() {
const featureFlags = useAdapter<FeatureFlagPort>("featureflags");
const config = featureFlags.getFeatureFlag<{
maxItems: number;
enableSearch: boolean;
theme: string;
}>("app-config");
return {
maxItems: config.value?.maxItems ?? 10,
enableSearch: config.value?.enableSearch ?? true,
theme: config.value?.theme ?? "light",
};
}A/B Testing
function ProductList() {
const featureFlags = useAdapter<FeatureFlagPort>("featureflags");
const layoutVariant = featureFlags.getFeatureFlag<string>("product-list-layout");
if (layoutVariant.variant === "grid") {
return <ProductGrid />;
} else if (layoutVariant.variant === "list") {
return <ProductList />;
}
return <ProductDefault />;
}Gradual Rollout
function NewFeature() {
const featureFlags = useAdapter<FeatureFlagPort>("featureflags");
// GrowthBook handles gradual rollout automatically
// based on user attributes (id, email, etc.)
const isEnabled = featureFlags.isEnabled("new-feature-rollout");
if (isEnabled) {
return <NewFeatureContent />;
}
return <OldFeatureContent />;
}UseCase with Feature Flags
import { FeatureFlagPort, HttpClientPort } from "@natify/core";
export class GetProductsUseCase {
constructor(
private readonly http: HttpClientPort,
private readonly featureFlags: FeatureFlagPort
) {}
async execute(): Promise<Product[]> {
// Check if use new endpoint
const useNewAPI = this.featureFlags.isEnabled("new-products-api");
const endpoint = useNewAPI ? "/api/v2/products" : "/api/v1/products";
const response = await this.http.get<Product[]>(endpoint);
return response.data;
}
}Initialization with Attributes
import { useEffect } from "react";
import { useAdapter, FeatureFlagPort, StoragePort } from "@natify/core";
function AppInitializer() {
const featureFlags = useAdapter<FeatureFlagPort>("featureflags");
const storage = useAdapter<StoragePort>("storage");
useEffect(() => {
const initializeFlags = async () => {
// Get saved user
const user = await storage.getItem<User>("user");
if (user) {
// Initialize with user attributes
await featureFlags.init({
id: user.id,
email: user.email,
plan: user.plan,
});
} else {
// Initialize without attributes (anonymous user)
await featureFlags.init();
}
};
initializeFlags();
}, []);
return null;
}Manually Refresh Flags
function RefreshFlagsButton() {
const featureFlags = useAdapter<FeatureFlagPort>("featureflags");
const handleRefresh = async () => {
try {
await featureFlags.refresh();
console.log("Feature flags refreshed");
} catch (error) {
console.error("Error refreshing flags:", error);
}
};
return <Button onPress={handleRefresh} title="Refresh Flags" />;
}Module Integration
import { createModule } from "@natify/core";
import { GetProductsUseCase } from "./usecases/GetProductsUseCase";
export const ProductsModule = createModule("products", "Products")
.requires("featureflags", "http")
.useCase("getProducts", (adapters) => {
return new GetProductsUseCase(adapters.http, adapters.featureflags);
})
.build();API
FeatureFlagPort
| Method | Description |
|--------|-------------|
| init(attributes?) | Initializes the service |
| getValue<T>(key, defaultValue?) | Gets flag value |
| isEnabled(key) | Checks if enabled |
| getFeatureFlag<T>(key) | Gets complete result |
| getFeatureFlags(keys) | Gets multiple flags |
| setAttributes(attributes) | Updates user attributes |
| getAttributes() | Gets current attributes |
| refresh() | Refreshes flags from server |
| clearAttributes() | Clears attributes (logout) |
FeatureFlagResult
interface FeatureFlagResult<T> {
value: T | null; // Flag value
enabled: boolean; // If enabled
exists: boolean; // If flag exists
variant?: string; // Assigned variant
source?: string; // Evaluation source
}UserAttributes
interface UserAttributes {
id?: string;
email?: string;
name?: string;
roles?: string[];
plan?: string;
country?: string;
[key: string]: unknown; // Custom attributes
}Best Practices
Feature Flag Names
✅ Good names:
premium-featurenew-checkout-flowdark-mode-toggleproduct-grid-layout
❌ Avoid:
feature1,flag1(too generic)PremiumFeature(use kebab-case)new_feature(use kebab-case, not snake_case)
Default Values
// Always provide default values
const theme = featureFlags.getValue<string>("app-theme", "light");
const maxItems = featureFlags.getValue<number>("max-items", 10);
const enabled = featureFlags.isEnabled("new-feature"); // false by defaultUser Attributes
// Set relevant attributes for targeting
featureFlags.setAttributes({
id: user.id, // For gradual rollout
email: user.email, // For email targeting
plan: user.plan, // For premium features
country: user.country, // For geographic features
roles: user.roles, // For role-based features
});Refresh After Changes
// After updating important attributes
featureFlags.setAttributes({ plan: "premium" });
await featureFlags.refresh(); // Apply new targeting rulesNotes
- Initialization: Always call
init()before using flags - Attributes: User attributes are used for targeting and gradual rollout
- Streaming: Requires
react-native-ssefor real-time updates - Remote Evaluation: Requires proxy server for better security
- Default Values: Always provide default values to avoid errors
- Errors: Errors are logged but don't throw exceptions to avoid interrupting flow
