React Integration
Featsync works great with React. Here’s how to set up hooks for a clean integration.
Setup
First, create a provider and hooks file:
import { useState, useEffect, createContext, useContext, useRef } from 'react';import { Featsync } from '@featsync/sdk';
// Create a single SDK instanceconst featsync = new Featsync({ apiKey: process.env.REACT_APP_FEATSYNC_API_KEY, cacheMs: 60000, persist: true,});
// Contextconst FeatsyncContext = createContext(null);
// Provider componentexport function FeatsyncProvider({ children }) { const [flags, setFlags] = useState({}); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const isMounted = useRef(true);
const fetchFlags = async (forceRefresh = false) => { try { if (forceRefresh) { featsync.clearCache(); } const allFlags = await featsync.getAllFlags(); if (isMounted.current) { setFlags(allFlags); setLoading(false); setError(null); } } catch (err) { if (isMounted.current) { setError(err); setLoading(false); } } };
useEffect(() => { isMounted.current = true; fetchFlags(); return () => { isMounted.current = false; }; }, []);
const refresh = () => fetchFlags(true);
return ( <FeatsyncContext.Provider value={{ flags, loading, error, refresh }}> {children} </FeatsyncContext.Provider> );}
// Hook: Get all flagsexport function useFlags() { const context = useContext(FeatsyncContext); if (!context) { throw new Error('useFlags must be used within a FeatsyncProvider'); } return context;}
// Hook: Get a single flagexport function useFlag(key, defaultValue = false) { const { flags, loading } = useFlags();
if (loading) { return { enabled: defaultValue, loading: true }; }
return { enabled: flags[key]?.enabled ?? defaultValue, percentage: flags[key]?.percentage ?? null, loading: false, };}
// Hook: Check flag for a specific user (percentage rollouts)export function useFlagForUser(key, userId, defaultValue = false) { const [enabled, setEnabled] = useState(defaultValue); const [loading, setLoading] = useState(true); const { flags } = useFlags();
useEffect(() => { let mounted = true;
featsync .isEnabledForUser(key, userId, defaultValue) .then((result) => { if (mounted) { setEnabled(result); setLoading(false); } }) .catch(() => { if (mounted) { setEnabled(defaultValue); setLoading(false); } });
return () => { mounted = false; }; }, [key, userId, defaultValue]);
return { enabled, percentage: flags[key]?.percentage ?? null, loading, };}
// Export SDK instance for direct accessexport { featsync };Wrap Your App
Add the provider to your app’s root:
import { FeatsyncProvider } from './lib/featsync';
function App() { return ( <FeatsyncProvider> <YourApp /> </FeatsyncProvider> );}Using the Hooks
Simple Boolean Flag
import { useFlag } from './lib/featsync';
function Navigation() { const { enabled, loading } = useFlag('new-navigation');
if (loading) { return <div>Loading...</div>; }
return enabled ? <NewNavigation /> : <OldNavigation />;}Percentage Rollout
import { useFlagForUser } from './lib/featsync';
function Checkout({ userId }) { const { enabled, loading, percentage } = useFlagForUser('new-checkout', userId);
if (loading) { return <div>Loading...</div>; }
return ( <div> {percentage && <small>Rollout: {percentage}%</small>} {enabled ? <NewCheckout /> : <OldCheckout />} </div> );}Check Multiple Flags
import { useFlags } from './lib/featsync';
function Dashboard() { const { flags, loading, refresh } = useFlags();
if (loading) { return <div>Loading...</div>; }
return ( <div> {flags['show-analytics']?.enabled && <AnalyticsWidget />} {flags['show-notifications']?.enabled && <NotificationWidget />} <button onClick={refresh}>Refresh Flags</button> </div> );}Conditional Rendering Patterns
Show/Hide Components
function FeatureSection() { const { enabled } = useFlag('new-feature');
if (!enabled) return null;
return <NewFeature />;}Toggle Between Implementations
function PaymentForm() { const { enabled } = useFlag('use-stripe');
return enabled ? <StripePayment /> : <PayPalPayment />;}Loading States
function Feature() { const { enabled, loading } = useFlag('my-feature');
if (loading) { return <Skeleton />; }
return enabled ? <NewFeature /> : <OldFeature />;}TypeScript
Here’s the TypeScript version of the hooks:
import { useState, useEffect, createContext, useContext, useRef, ReactNode } from 'react';import { Featsync, FlagsMap } from '@featsync/sdk';
interface FeatsyncContextValue { flags: FlagsMap; loading: boolean; error: Error | null; refresh: () => void;}
interface FlagValue { enabled: boolean; percentage: number | null; loading: boolean;}
const FeatsyncContext = createContext<FeatsyncContextValue | null>(null);
export function FeatsyncProvider({ children }: { children: ReactNode }) { // ... same implementation}
export function useFlags(): FeatsyncContextValue { const context = useContext(FeatsyncContext); if (!context) { throw new Error('useFlags must be used within a FeatsyncProvider'); } return context;}
export function useFlag(key: string, defaultValue = false): FlagValue { // ... same implementation}
export function useFlagForUser(key: string, userId: string, defaultValue = false): FlagValue { // ... same implementation}Next.js App Router
For Next.js App Router, make the provider a client component:
'use client';
import { FeatsyncProvider } from '@/lib/featsync';
export function Providers({ children }) { return <FeatsyncProvider>{children}</FeatsyncProvider>;}import { Providers } from './providers/featsync';
export default function RootLayout({ children }) { return ( <html> <body> <Providers>{children}</Providers> </body> </html> );}Server Components
For Server Components, use the SDK directly:
import { Featsync } from '@featsync/sdk';
const featsync = new Featsync({ apiKey: process.env.FEATSYNC_API_KEY,});
export default async function Page() { const showBanner = await featsync.isEnabled('show-banner');
return ( <div> {showBanner && <Banner />} <Content /> </div> );}Next Steps
- Learn about percentage rollouts for gradual releases
- Set up multiple environments
- View the API reference