Skip to Content

React

React-specific code example with hooks and components for seamless feature flag integration.

Requirements

  • React 16.8+ (hooks support)
  • Node.js 16+

Installation

npm install @darkfeature/sdk-react # or yarn add @darkfeature/sdk-react # or pnpm add @darkfeature/sdk-react

Provider Setup

Wrap your app with the DarkFeatureProvider:

import React from 'react' import { DarkFeatureProvider } from '@darkfeature/sdk-react' import App from './App' interface AppContext { userId: string email: string version: string } function Root() { const context: AppContext = { userId: '123', email: '[email protected]', version: '1.0.0', } return ( <DarkFeatureProvider config={{ apiKey: 'your-api-key', // Store in environment variable context, }}> <App /> </DarkFeatureProvider> ) } export default Root

Component-Based Approach

DarkFeature Component

Use the declarative DarkFeature component for simple feature flag rendering:

import React from 'react' import { DarkFeature } from '@darkfeature/sdk-react' function YourComponent() { return ( <div> {/* Declarative component approach */} <DarkFeature feature="new-navigation" fallback={false} variations={{ true: <NewNavigation />, false: <OldNavigation />, }} /> </div> ) } const NewNavigation: React.FC = () => ( <div>🚀 New Navigation Enabled!</div> ) const OldNavigation: React.FC = () => ( <div>📦 Classic Navigation</div> )

Advanced Component Usage

// With context override <DarkFeature feature="premium-features" fallback={false} context={{ segment: 'premium' }} variations={{ true: <PremiumFeatures />, false: <BasicFeatures />, }} /> // With custom fallback component <DarkFeature feature="beta-feature" fallback={<ComingSoon />} variations={{ true: <BetaFeature />, false: <ComingSoon />, }} />

Hook-Based Approach

useFeature Hook

Get a single feature flag value with the useFeature hook:

import React from 'react' import { useFeature } from '@darkfeature/sdk-react' function MyComponent() { // Single feature with hook const { feature: isEnabled } = useFeature('new-navigation', { fallback: false, }) return <div>{isEnabled ? <NewNavigation /> : <OldNavigation />}</div> }

useFeature with Context Override

// Feature with context override and typing const { feature: expFeature } = (useFeature < 'control') | 'variant-a' | ('variant-b' > ('experiment-feature', { fallback: 'control', context: { segment: 'premium' }, })) return ( <div> {expFeature === 'variant-a' && <VariantA />} {expFeature === 'variant-b' && <VariantB />} {expFeature === 'control' && <Control />} </div> )

useFeatures Hook

Get multiple feature flags at once:

import React from 'react' import { useFeatures } from '@darkfeature/sdk-react' interface FeatureSet { 'feature-1': boolean 'feature-2': string 'feature-3': null } function Dashboard() { // Multiple features at once with typing const { features } = useFeatures<FeatureSet>({ features: { 'feature-1': false, 'feature-2': 'default-value', 'feature-3': null, }, context: { page: 'dashboard' }, }) return ( <div> {features['feature-1'] && <Feature1Component />} <Feature2Component value={features['feature-2']} /> {features['feature-3'] && <Feature3Component />} </div> ) }

Advanced Provider Configuration

import { DarkFeatureProvider } from '@darkfeature/sdk-react' function App() { const context = { userId: '123', email: '[email protected]', version: '1.0.0', } return ( <DarkFeatureProvider config={{ apiKey: process.env.REACT_APP_DARKFEATURE_API_KEY, context, baseUrl: 'https://api.darkfeature.com', timeout: 5000, cache: { enabled: true, ttl: 300000, // 5 minutes maxSize: 1000, }, debug: process.env.NODE_ENV === 'development', }}> <YourApp /> </DarkFeatureProvider> ) }

TypeScript Support

import React from 'react' import { useFeature, DarkFeature } from '@darkfeature/sdk-react' interface UserContext { userId: string email: string plan?: 'free' | 'premium' | 'enterprise' } interface MyComponentProps { userId: string } const MyComponent: React.FC<MyComponentProps> = ({ userId }) => { // Single feature with proper typing const { feature: isEnabled } = useFeature('premium-features', { fallback: false, }) // Feature with custom type const { feature: theme } = useFeature<'light' | 'dark'>('theme', { fallback: 'light', }) return ( <div className={`theme-${theme}`}> <DarkFeature feature='premium-features' fallback={false} variations={{ true: <PremiumFeatures userId={userId} />, false: <BasicFeatures />, }} /> </div> ) }

Context Management

Updating Context

import React, { useCallback } from 'react' import { useDarkFeatureContext } from '@darkfeature/sdk-react' function UserProfile() { const { updateContext, context } = useDarkFeatureContext() const handleUpgrade = useCallback(() => { updateContext({ ...context, plan: 'premium', upgradeDate: new Date().toISOString(), }) }, [updateContext, context]) return ( <div> <h2>Current Plan: {context.plan || 'free'}</h2> <button onClick={handleUpgrade}>Upgrade to Premium</button> </div> ) }

Getting Current Context

import { useDarkFeatureContext } from '@darkfeature/sdk-react' function ContextDisplay() { const { context } = useDarkFeatureContext() return ( <div> <p>User ID: {context.userId}</p> <p>Email: {context.email}</p> <p>Version: {context.version}</p> </div> ) }

Error Handling

import React from 'react' import { useFeature, DarkFeatureProvider } from '@darkfeature/sdk-react' function ErrorBoundary({ children }) { return ( <DarkFeatureProvider config={{ apiKey: 'your-api-key', context: { userId: '123' }, }} onError={error => { console.error('DarkFeature error:', error) // Send to error tracking service }} fallbackComponent={ <div> Something went wrong with feature flags. Using default behavior. </div> }> {children} </DarkFeatureProvider> ) } function ComponentWithErrorHandling() { const { feature: isEnabled, error } = useFeature('new-feature', { fallback: false, }) // Handle errors at component level if (error) { return <div>Using default features due to error</div> } return <div>{isEnabled && <NewFeature />}</div> }

Server-Side Rendering (Next.js)

// pages/_app.js import { DarkFeatureProvider } from '@darkfeature/sdk-react' function MyApp({ Component, pageProps, featureFlags }) { const context = { userId: pageProps.user?.id || 'anonymous', email: pageProps.user?.email || '', version: '1.0.0', } return ( <DarkFeatureProvider config={{ apiKey: process.env.NEXT_PUBLIC_DARKFEATURE_API_KEY, context, }} initialFlags={featureFlags}> <Component {...pageProps} /> </DarkFeatureProvider> ) } // pages/index.js export async function getServerSideProps(context) { const user = getUserFromContext(context) // Pre-fetch feature flags on server const featureFlags = await fetchFeatureFlags(user) return { props: { user, featureFlags, }, } }

Testing

import React from 'react' import { render } from '@testing-library/react' import { DarkFeatureProvider } from '@darkfeature/sdk-react' import MyComponent from './MyComponent' // Mock provider for testing function TestWrapper({ children, mockFlags = {} }) { const context = { userId: 'test-user', email: '[email protected]', version: '1.0.0', } return ( <DarkFeatureProvider config={{ apiKey: 'test-key', context, }} mockFlags={mockFlags}> {children} </DarkFeatureProvider> ) } test('shows new feature when enabled', () => { const { getByText } = render( <TestWrapper mockFlags={{ 'new-feature': true }}> <MyComponent /> </TestWrapper>, ) expect(getByText('New Feature')).toBeInTheDocument() }) test('uses fallback when feature is disabled', () => { const { getByText } = render( <TestWrapper mockFlags={{ 'new-feature': false }}> <MyComponent /> </TestWrapper>, ) expect(getByText('Old Feature')).toBeInTheDocument() })

Performance Optimization

import React, { memo } from 'react' import { useFeature } from '@darkfeature/sdk-react' // Memoize components that depend on feature flags const ExpensiveComponent = memo(() => { const { feature: isEnabled } = useFeature('expensive-feature', { fallback: false, }) if (!isEnabled) { return null } return <VeryExpensiveFeature /> }) // Use feature flags in useMemo/useCallback dependencies function OptimizedComponent() { const { feature: isFeatureAEnabled } = useFeature('feature-a', { fallback: false, }) const memoizedValue = useMemo(() => { return isFeatureAEnabled ? 'A' : 'B' }, [isFeatureAEnabled]) return <div>{memoizedValue}</div> } // Batch feature evaluations function BatchComponent() { const { features } = useFeatures({ features: { 'feature-a': false, 'feature-b': false, 'feature-c': false, }, }) return ( <div> {features['feature-a'] && <FeatureA />} {features['feature-b'] && <FeatureB />} {features['feature-c'] && <FeatureC />} </div> ) }

Best Practices

1. Provider Placement

Place the provider as high as possible in your component tree:

// ✅ Good - Provider at root level function App() { return ( <DarkFeatureProvider config={{ apiKey: 'key', context: {} }}> <Router> <Routes /> </Router> </DarkFeatureProvider> ) } // ❌ Avoid - Provider too low in tree function SomeComponent() { return ( <div> <DarkFeatureProvider config={{ apiKey: 'key', context: {} }}> <ChildComponent /> </DarkFeatureProvider> </div> ) }

2. Error Boundaries

Wrap the provider with error boundaries:

import { ErrorBoundary } from 'react-error-boundary' function App() { return ( <ErrorBoundary fallback={<ErrorFallback />}> <DarkFeatureProvider config={{ apiKey: 'key', context: {} }}> <YourApp /> </DarkFeatureProvider> </ErrorBoundary> ) }

3. Loading States

Always handle loading states gracefully:

function MyComponent() { const { feature: isEnabled, loading } = useFeature('new-feature', { fallback: false, }) if (loading) { return <LoadingSpinner /> } return isEnabled ? <NewFeature /> : <OldFeature /> }

4. Memoization

Use React.memo for components that depend on feature flags:

const FeatureDependentComponent = memo(({ userId }) => { const { feature: isEnabled } = useFeature('premium-features', { fallback: false, }) return isEnabled ? <PremiumUI userId={userId} /> : <BasicUI userId={userId} /> })

5. TypeScript Usage

Use proper typing for better development experience:

// Define feature types type FeatureFlags = { 'new-navigation': boolean theme: 'light' | 'dark' experiment: 'control' | 'variant-a' | 'variant-b' } // Use in components const { feature: theme } = useFeature<FeatureFlags['theme']>('theme', { fallback: 'light', })

Troubleshooting

Common Issues

Provider Not Found

// Make sure DarkFeatureProvider wraps your component function MyComponent() { const { feature } = useFeature('feature', { fallback: false }) // ❌ Error return <div>...</div> } // ✅ Correct function App() { return ( <DarkFeatureProvider config={{ apiKey: 'key', context: {} }}> <MyComponent /> </DarkFeatureProvider> ) }

Context Not Updating

// Use updateContext to change context const { updateContext, context } = useDarkFeatureContext() const handleLogin = user => { updateContext({ ...context, userId: user.id, email: user.email, }) }

Feature Always Returns Fallback

// Check if context is properly set const { context } = useDarkFeatureContext() console.log('Current context:', context) // Check feature configuration const { feature, error } = useFeature('test-feature', { fallback: 'test' }) console.log('Feature value:', feature, 'Error:', error)
Last updated on