React SDK Integration
3. Install the SDK
# Using npm
npm install @flagpole/react
# Using yarn
yarn add @flagpole/react
4. Initialize in Your Application
// App.tsx
import { FeatureFlagProvider } from "@flagpole/react";
function App() {
return (
<FeatureFlagProvider
apiKey="fp_live_your_api_key"
environments={["development"]} // optional, if nothing is passed, then all environments will be shown (production, staging and development)
>
<FeatureComponent />
</FeatureFlagProvider>
);
}
export default App;
5. Use Feature Flags
Basic Hook Usage
import { useFeatureFlag, useFeatureFlags } from "@flagpole/react";
export const FeatureComponent = () => {
// Get all feature flags
const { flags, isLoading, error } = useFeatureFlags();
// Get a specific feature flag
const isNewFeatureEnabled = useFeatureFlag("newFeature");
if (isLoading) return <div>Loading flags...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h2>Feature Flags Test</h2>
{/* Use a specific flag */}
{isNewFeatureEnabled && <NewFeature />}
{/* Display all available flags */}
<h3>All Flags:</h3>
<pre>{JSON.stringify(flags, null, 2)}</pre>
</div>
);
};
Higher-Order Component (HOC) Usage
import { withFeatureFlag } from "@flagpole/react";
// Component that should only render when flag is enabled
const NewDashboard = () => {
return <div>New Dashboard Feature!</div>;
};
// Wrap component with feature flag
const ConditionalDashboard = withFeatureFlag(NewDashboard, "newDashboard");
// With fallback component
const OldDashboard = () => {
return <div>Old Dashboard</div>;
};
const DashboardWithFallback = withFeatureFlag(
NewDashboard,
"newDashboard",
OldDashboard
);
// Usage in your app
function App() {
return (
<div>
<ConditionalDashboard />
<DashboardWithFallback />
</div>
);
}
Feature Wrapper Component
import { useFeatureFlag } from "@flagpole/react";
interface FeatureWrapperProps {
flagKey: string;
children: React.ReactNode;
fallback?: React.ReactNode;
}
const FeatureWrapper: React.FC<FeatureWrapperProps> = ({
flagKey,
children,
fallback = null,
}) => {
const isEnabled = useFeatureFlag(flagKey);
return isEnabled ? <>{children}</> : <>{fallback}</>;
};
// Usage
const MyComponent = () => {
return (
<div>
<FeatureWrapper
flagKey="premiumFeature"
fallback={<div>Premium feature not available</div>}
>
<PremiumContent />
</FeatureWrapper>
</div>
);
};
Available Hooks and Components
useFeatureFlags
Returns all feature flags for the current project:
const {
flags, // Record<string, FeatureFlag> containing all flags
isLoading, // Boolean indicating loading state
error, // Error object if something went wrong
isFeatureEnabled, // Function to check specific flag: (flagName: string) => boolean
} = useFeatureFlags();
useFeatureFlag
Returns the state of a specific feature flag:
const isEnabled = useFeatureFlag("flagKey"); // Returns boolean
withFeatureFlag
Higher-order component that conditionally renders based on feature flag:
const WrappedComponent = withFeatureFlag(
YourComponent,
"featureFlagName",
OptionalFallbackComponent
);
Advanced Usage Patterns
Conditional Rendering with Multiple Flags
import { useFeatureFlags } from "@flagpole/react";
const AdvancedFeatureComponent = () => {
const { isFeatureEnabled } = useFeatureFlags();
const showAdvancedUI = isFeatureEnabled("advancedUI");
const showBetaFeatures = isFeatureEnabled("betaFeatures");
const showPremiumContent = isFeatureEnabled("premiumContent");
return (
<div>
{showAdvancedUI && <AdvancedUIComponent />}
{showBetaFeatures && showPremiumContent && <PremiumBetaFeature />}
{!showAdvancedUI && <StandardUIComponent />}
</div>
);
};
Custom Hook for Complex Logic
import { useFeatureFlags, useFeatureFlag } from "@flagpole/react";
const useNavigation = () => {
const newNavEnabled = useFeatureFlag("newNavigation");
const adminPanelEnabled = useFeatureFlag("adminPanel");
const { isLoading } = useFeatureFlags();
const navigationItems = useMemo(() => {
const items = [
{ label: "Home", path: "/" },
{ label: "About", path: "/about" },
];
if (newNavEnabled) {
items.push({ label: "Dashboard", path: "/dashboard" });
}
if (adminPanelEnabled) {
items.push({ label: "Admin", path: "/admin" });
}
return items;
}, [newNavEnabled, adminPanelEnabled]);
return { navigationItems, isLoading };
};
// Usage
const Navigation = () => {
const { navigationItems, isLoading } = useNavigation();
if (isLoading) return <div>Loading navigation...</div>;
return (
<nav>
{navigationItems.map((item) => (
<a key={item.path} href={item.path}>
{item.label}
</a>
))}
</nav>
);
};
React Router Integration
import { Routes, Route, Navigate } from "react-router-dom";
import { useFeatureFlag } from "@flagpole/react";
const ProtectedRoute = ({ children, flagKey, fallbackPath = "/" }) => {
const isEnabled = useFeatureFlag(flagKey);
return isEnabled ? children : <Navigate to={fallbackPath} replace />;
};
const AppRoutes = () => {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route
path="/beta"
element={
<ProtectedRoute flagKey="betaAccess" fallbackPath="/coming-soon">
<BetaFeature />
</ProtectedRoute>
}
/>
<Route
path="/admin"
element={
<ProtectedRoute flagKey="adminPanel" fallbackPath="/unauthorized">
<AdminPanel />
</ProtectedRoute>
}
/>
</Routes>
);
};
Performance Optimization with React.memo
import React, { memo } from "react";
import { useFeatureFlag } from "@flagpole/react";
const ExpensiveFeatureComponent = memo(() => {
const isEnabled = useFeatureFlag("expensiveFeature");
if (!isEnabled) return null;
return <div>{/* Expensive rendering logic */}</div>;
});
// Component only re-renders when flag state changes
A/B Testing Implementation
import { useFeatureFlags } from "@flagpole/react";
const ABTestComponent = () => {
const { flags } = useFeatureFlags();
const experimentFlag = flags["checkoutExperiment"];
const variant = experimentFlag?.conditions?.variant || "control";
switch (variant) {
case "variantA":
return <CheckoutVariantA />;
case "variantB":
return <CheckoutVariantB />;
default:
return <CheckoutControl />;
}
};
Best Practices
API Key Security
- Store API keys in environment variables
- Use different keys for different environments
- Never commit API keys to source control
- Rotate keys if they're ever exposed
// Example using environment variables
<FeatureFlagProvider
apiKey={import.meta.env.VITE_FLAGPOLE_API_KEY}
environments={import.meta.env.VITE_ENVIRONMENT ? [import.meta.env.VITE_ENVIRONMENT] : undefined}
>
Error Handling
Always handle loading and error states:
const { flags, isLoading, error } = useFeatureFlags();
if (isLoading) {
return <LoadingSpinner />;
}
if (error) {
return (
<ErrorBoundary>
<ErrorMessage
message={error.message}
onRetry={() => window.location.reload()}
/>
</ErrorBoundary>
);
}
Loading States with Suspense
import { Suspense } from "react";
const App = () => {
return (
<FeatureFlagProvider apiKey="your-api-key">
<Suspense fallback={<div>Loading feature flags...</div>}>
<MainApp />
</Suspense>
</FeatureFlagProvider>
);
};
Feature Flag Naming
Use descriptive, consistent names:
- Include feature context
- Use camelCase
- Be specific but concise
Examples:
newDashboard
betaUserProfile
experimentalSearch
Testing
Test both enabled and disabled states:
// feature.test.tsx
import { render } from "@testing-library/react";
import { FeatureFlagProvider } from "@flagpole/react";
const MockFeatureFlagProvider = ({ children, flags = {} }) => {
// Mock provider for testing
return (
<FeatureFlagProvider apiKey="test-key">{children}</FeatureFlagProvider>
);
};
describe("FeatureComponent", () => {
it("shows new feature when flag is enabled", () => {
// Mock the flag as enabled
const { getByText } = render(
<MockFeatureFlagProvider>
<FeatureComponent />
</MockFeatureFlagProvider>
);
expect(getByText("New Feature")).toBeInTheDocument();
});
it("hides new feature when flag is disabled", () => {
// Mock the flag as disabled
const { queryByText } = render(
<MockFeatureFlagProvider>
<FeatureComponent />
</MockFeatureFlagProvider>
);
expect(queryByText("New Feature")).not.toBeInTheDocument();
});
});
TypeScript Support
Define flag types for better type safety:
// types/flags.ts
export interface FeatureFlags {
newDashboard: boolean;
betaFeatures: boolean;
premiumContent: boolean;
experimentalSearch: boolean;
}
// Custom hook with type safety
export const useTypedFeatureFlag = <K extends keyof FeatureFlags>(
flagKey: K
): boolean => {
return useFeatureFlag(flagKey);
};
// Usage
const isNewDashboardEnabled = useTypedFeatureFlag("newDashboard"); // Fully typed
Performance Considerations
- Use React.memo for components that depend on feature flags
- Avoid creating new objects in render methods
- Consider lazy loading components behind feature flags
import { lazy, Suspense } from "react";
import { useFeatureFlag } from "@flagpole/react";
// Lazy load expensive components
const ExpensiveFeature = lazy(() => import("./ExpensiveFeature"));
const OptimizedFeatureComponent = () => {
const isEnabled = useFeatureFlag("expensiveFeature");
if (!isEnabled) return null;
return (
<Suspense fallback={<div>Loading feature...</div>}>
<ExpensiveFeature />
</Suspense>
);
};
Error Boundaries
Wrap feature flag components in error boundaries:
class FeatureFlagErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error("Feature flag error:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
this.props.fallback || (
<div>Something went wrong with feature flags.</div>
)
);
}
return this.props.children;
}
}
// Usage
const App = () => {
return (
<FeatureFlagProvider apiKey="your-api-key">
<FeatureFlagErrorBoundary>
<MainApp />
</FeatureFlagErrorBoundary>
</FeatureFlagProvider>
);
};