React
A React SDK for Supaship that provides hooks and components for feature flag management with full TypeScript type safety.
Requirements
- React 16.8+ (hooks support)
- Use JavaScript SDK for react versions < 16.8 (see JavaScript code examples)
Installation
pnpm add @supashiphq/react-sdkQuick Start
import {
SupaProvider,
useFeature,
FeaturesWithFallbacks,
} from '@supashiphq/react-sdk'
// Define your features with type safety
const FEATURE_FLAGS = {
'new-header': false,
'theme-config': { mode: 'dark' as 'dark' | 'light', showLogo: true },
'beta-features': [] as string[],
} satisfies FeaturesWithFallbacks
function App() {
return (
<SupaProvider
config={{
apiKey: 'your-api-key',
environment: 'production',
features: FEATURE_FLAGS,
context: {
userID: '123',
email: '[email protected]',
},
}}>
<YourApp />
</SupaProvider>
)
}
function YourApp() {
// Hook returns { feature, isLoading, error, ... }
const { feature: newHeader, isLoading } = useFeature('new-header')
if (isLoading) return <div>Loading...</div>
return <div>{newHeader ? <NewHeader /> : <OldHeader />}</div>
}Type-Safe Feature Flags
For full TypeScript type safety, define your features and augment the Features interface:
// lib/features.ts
import { FeaturesWithFallbacks, InferFeatures } from '@supashiphq/react-sdk'
export const FEATURE_FLAGS = {
'new-header': false,
'theme-config': {
mode: 'dark' as 'dark' | 'light',
primaryColor: '#007bff',
showLogo: true,
},
'beta-features': [] as string[],
'disabled-feature': null,
} satisfies FeaturesWithFallbacks
// Type augmentation for global type safety, it is required
declare module '@supashiphq/react-sdk' {
interface Features extends InferFeatures<typeof FEATURE_FLAGS> {}
}Now useFeature and useFeatures will have full type safety:
function MyComponent() {
// TypeScript knows 'new-header' is valid and feature is boolean | null
const { feature } = useFeature('new-header')
// TypeScript knows 'theme-config' returns the exact object shape
const { feature: config } = useFeature('theme-config')
// config is { mode: 'dark' | 'light', primaryColor: string, showLogo: boolean } | null
// TypeScript will error on invalid feature names
const { feature: invalid } = useFeature('non-existent-feature') // ❌ Type error
}API Reference
For complete API documentation, see the React SDK API Reference.
Best Practices
1. Use Type Augmentation for Type Safety
// ✅ Good - type augmentation for global type safety
declare module '@supashiphq/react-sdk' {
interface Features extends InferFeatures<typeof FEATURE_FLAGS> {}
}
// Now all useFeature calls are type-safe
const { feature } = useFeature('new-header') // ✅ TypeScript knows this is boolean | null
const { feature } = useFeature('invalid') // ❌ TypeScript error2. Use Context for User Targeting
function App() {
const { user } = useAuth()
return (
<SupaProvider
config={{
apiKey: 'your-api-key',
features: FEATURE_FLAGS,
context: {
userId: user?.id,
email: user?.email,
plan: user?.subscriptionPlan,
version: process.env.REACT_APP_VERSION,
},
}}>
<YourApp />
</SupaProvider>
)
}3. Batch Feature Requests
// ✅ Good - single API call
const { features } = useFeatures(['feature-1', 'feature-2', 'feature-3'])
// ❌ Less efficient - multiple API calls
const feature1 = useFeature('feature-1')
const feature2 = useFeature('feature-2')
const feature3 = useFeature('feature-3')4. Handle Loading States
function MyComponent() {
const { user, isLoading: userLoading } = useUser()
const { features, isLoading: featuresLoading } = useFeatures(
['user-specific-feature'],
{
context: { userId: user?.id },
shouldFetch: !userLoading && !!user,
},
)
if (userLoading || featuresLoading) return <Skeleton />
return <div>{features['user-specific-feature'] && <SpecialContent />}</div>
}5. Update Context Reactively
function UserDashboard() {
const { updateContext } = useFeatureContext()
const [currentPage, setCurrentPage] = useState('dashboard')
// Update context when navigation changes
useEffect(() => {
updateContext({ currentPage })
}, [currentPage, updateContext])
return (
<div>
<Navigation onPageChange={setCurrentPage} />
<PageContent page={currentPage} />
</div>
)
}Framework Integration
Next.js App Router (Next.js 13+)
// app/providers.tsx
'use client'
import { SupaProvider, FeaturesWithFallbacks } from '@supashiphq/react-sdk'
const FEATURE_FLAGS = {
'new-hero': false,
theme: { mode: 'light' as 'light' | 'dark' },
} satisfies FeaturesWithFallbacks
export function Providers({ children }: { children: React.ReactNode }) {
return (
<SupaProvider
config={{
apiKey: process.env.NEXT_PUBLIC_SUPASHIP_API_KEY!,
environment: process.env.NODE_ENV!,
features: FEATURE_FLAGS,
}}>
{children}
</SupaProvider>
)
}
// app/layout.tsx
import { Providers } from './providers'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang='en'>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
// app/page.tsx
;('use client')
import { useFeature } from '@supashiphq/react-sdk'
export default function HomePage() {
const { feature: newHero } = useFeature('new-hero')
return <main>{newHero ? <NewHeroSection /> : <OldHeroSection />}</main>
}Next.js Pages Router (Next.js 12 and below)
// pages/_app.tsx
import { SupaProvider, FeaturesWithFallbacks } from '@supashiphq/react-sdk'
import type { AppProps } from 'next/app'
const FEATURE_FLAGS = {
'new-homepage': false,
} satisfies FeaturesWithFallbacks
export default function App({ Component, pageProps }: AppProps) {
return (
<SupaProvider
config={{
apiKey: process.env.NEXT_PUBLIC_SUPASHIP_API_KEY!,
environment: process.env.NODE_ENV!,
features: FEATURE_FLAGS,
}}>
<Component {...pageProps} />
</SupaProvider>
)
}Vite / Create React App
// src/main.tsx or src/index.tsx
import { SupaProvider, FeaturesWithFallbacks } from '@supashiphq/react-sdk'
const FEATURE_FLAGS = {
'new-ui': false,
theme: { mode: 'light' as 'light' | 'dark' },
} satisfies FeaturesWithFallbacks
function App() {
return (
<SupaProvider
config={{
apiKey: import.meta.env.VITE_SUPASHIP_API_KEY, // Vite
// or
apiKey: process.env.REACT_APP_SUPASHIP_API_KEY, // CRA
environment: import.meta.env.MODE,
features,
}}>
<YourApp />
</SupaProvider>
)
}Development Toolbar
The SDK includes a development toolbar for testing and debugging feature flags locally.
<SupaProvider
config={{ ... }}
toolbar={{
show: 'auto', // 'auto' | 'always' | 'never'
position: 'bottom-right', // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
}}
>
<YourApp />
</SupaProvider>'auto': Shows toolbar in development environments only (default)'always': Always shows toolbar'never': Never shows toolbar
The toolbar allows you to:
- View all available feature flags
- Override feature values locally
- See feature value types and current values
- Clear local overrides
Plugins
Extend the SDK functionality with custom plugins:
import { SupaPlugin } from '@supashiphq/react-sdk'
const analyticsPlugin: SupaPlugin = {
name: 'analytics',
onInit: async (availableFeatures, context) => {
console.log('SDK initialized with features:', Object.keys(availableFeatures))
},
afterGetFeatures: async (features, context) => {
// Track feature usage
analytics.track('features_fetched', { features: Object.keys(features) })
},
}
<SupaProvider config={config} plugins={[analyticsPlugin]}>
<App />
</SupaProvider>Testing
Mocking Feature Flags in Tests
// test-utils/providers.tsx
import { SupaProvider, FeaturesWithFallbacks } from '@supashiphq/react-sdk'
export function TestProviders({ children, features = {} }) {
const testFeatures = { ...features } satisfies FeaturesWithFallbacks
return (
<SupaProvider
config={{
apiKey: 'test-key',
environment: 'test',
features: { ...features } satisfies FeaturesWithFallbacks,
}}>
{children}
</SupaProvider>
)
}Example Test
// MyComponent.test.tsx
import { render, screen } from '@testing-library/react'
import { TestProviders } from '../test-utils/providers'
import MyComponent from './MyComponent'
describe('MyComponent', () => {
it('shows new feature when enabled', () => {
render(
<TestProviders features={{ 'new-feature': true }}>
<MyComponent />
</TestProviders>,
)
expect(screen.getByText('New Feature Content')).toBeInTheDocument()
})
it('shows old feature when disabled', () => {
render(
<TestProviders features={{ 'new-feature': false }}>
<MyComponent />
</TestProviders>,
)
expect(screen.getByText('Old Feature Content')).toBeInTheDocument()
})
})Troubleshooting
Common Issues
Provider Not Found Error
Error: useFeature must be used within a SupaProviderSolution: Ensure your component is wrapped in a SupaProvider:
// ✅ Correct
function App() {
return (
<SupaProvider config={{ ... }}>
<MyComponent />
</SupaProvider>
)
}
// ❌ Incorrect
function App() {
return <MyComponent /> // Missing provider
}Features Not Loading
- Check API key: Verify your API key is correct
- Check network: Open browser dev tools and check network requests
- Check features config: Ensure features are defined in the config
Type Errors
Property 'my-feature' does not exist on type 'Features'Solution: Add type augmentation:
import { SupaFeatures } from '@supashiphq/react-sdk'
import { FEATURE_FLAGS } from './features'
declare module '@supashiphq/react-sdk' {
interface Features extends SupaFeatures<typeof FEATURE_FLAGS> {}
}Last updated on