Skip to main content

Feature Flags - Frontend Integration Guide

Overview​

Feature flags allow you to control feature availability across environments (Development, Staging, Production). This guide covers integrating feature flags into the FishingLog frontend applications.

API Endpoints​

Get Current Environment Feature Flags​

GET /api/admin/feature-flags/current-environment

Response:

{
"environment": "Development",
"featureFlags": {
"Circles": true,
"Tournaments": true,
"Charters": false
}
}

Get All Feature Flags (Admin Only)​

GET /api/admin/feature-flags

Response:

[
{
"id": 1,
"key": "Circles",
"name": "Circles Feature",
"description": "Custom friend groups for targeted sharing",
"category": "Social",
"isEnabledInDevelopment": true,
"isEnabledInStaging": true,
"isEnabledInProduction": false,
"isEnabledInCurrentEnvironment": true,
"currentEnvironment": "Development",
"isActive": true
}
]

Implementation Strategy​

1. Feature Flag Service/Hook​

Create a service or React hook to fetch and cache feature flags:

// hooks/useFeatureFlags.ts
import { useQuery } from '@tanstack/react-query';

interface FeatureFlags {
[key: string]: boolean;
}

export function useFeatureFlags() {
return useQuery<FeatureFlags>({
queryKey: ['featureFlags'],
queryFn: async () => {
const response = await fetch('/api/admin/feature-flags/current-environment', {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await response.json();
return data.featureFlags;
},
staleTime: 5 * 60 * 1000, // Cache for 5 minutes
refetchOnWindowFocus: false,
});
}

export function useFeatureFlag(featureKey: string) {
const { data: flags } = useFeatureFlags();
return flags?.[featureKey] ?? false;
}

2. Feature Gate Component​

Create a component to conditionally render features:

// components/FeatureGate.tsx
import { useFeatureFlag } from '@/hooks/useFeatureFlags';

interface FeatureGateProps {
feature: string;
children: React.ReactNode;
fallback?: React.ReactNode;
}

export function FeatureGate({ feature, children, fallback = null }: FeatureGateProps) {
const isEnabled = useFeatureFlag(feature);

if (!isEnabled) {
return <>{fallback}</>;
}

return <>{children}</>;
}

3. Usage Examples​

Conditional Rendering​

// Hide Circles feature if disabled
<FeatureGate feature="Circles" fallback={null}>
<NavLink to="/circles">Circles</NavLink>
</FeatureGate>

Conditional Navigation​

// Filter navigation items based on feature flags
const navigationItems = [
{ path: '/dashboard', label: 'Dashboard' },
{ path: '/circles', label: 'Circles', feature: 'Circles' },
{ path: '/tournaments', label: 'Tournaments', feature: 'Tournaments' },
].filter(item => !item.feature || useFeatureFlag(item.feature));

Conditional Routes​

// Next.js App Router Example
// app/circles/page.tsx
import { redirect } from 'next/navigation';
import { useFeatureFlag } from '@/hooks/useFeatureFlags';

export default function CirclesPage() {
const isEnabled = useFeatureFlag('Circles');

if (!isEnabled) {
redirect('/404');
}

return <CirclesContent />;
}

React Router Example​

// Protected route component
import { Navigate } from 'react-router-dom';
import { useFeatureFlag } from '@/hooks/useFeatureFlags';

export function ProtectedFeatureRoute({
feature,
children
}: {
feature: string;
children: React.ReactNode
}) {
const isEnabled = useFeatureFlag(feature);

if (!isEnabled) {
return <Navigate to="/404" replace />;
}

return <>{children}</>;
}

// Usage in router
<Route
path="/circles"
element={
<ProtectedFeatureRoute feature="Circles">
<CirclesPage />
</ProtectedFeatureRoute>
}
/>

React Native Implementation​

// hooks/useFeatureFlags.ts (React Native)
import { useQuery } from '@tanstack/react-query';
import { useAuth } from './useAuth';

export function useFeatureFlags() {
const { token } = useAuth();

return useQuery({
queryKey: ['featureFlags'],
queryFn: async () => {
const response = await fetch(
'https://api.fishinglog.com/api/admin/feature-flags/current-environment',
{
headers: { 'Authorization': `Bearer ${token}` }
}
);
const data = await response.json();
return data.featureFlags;
},
staleTime: 5 * 60 * 1000,
});
}

// Usage in React Native
function CirclesScreen() {
const { data: flags } = useFeatureFlags();

if (!flags?.Circles) {
return (
<View>
<Text>This feature is not available in your environment.</Text>
</View>
);
}

return <CirclesContent />;
}

Admin Panel Integration​

Feature Flag Management Page​

// Admin Panel - Feature Flags Management
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

export function FeatureFlagsPage() {
const queryClient = useQueryClient();

const { data: flags } = useQuery({
queryKey: ['admin', 'featureFlags'],
queryFn: async () => {
const response = await fetch('/api/admin/feature-flags');
return response.json();
}
});

const updateFlag = useMutation({
mutationFn: async ({ key, updates }: { key: string; updates: any }) => {
const response = await fetch(`/api/admin/feature-flags/${key}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(updates)
});
return response.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin', 'featureFlags'] });
queryClient.invalidateQueries({ queryKey: ['featureFlags'] });
}
});

return (
<div>
<h1>Feature Flags</h1>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Development</th>
<th>Staging</th>
<th>Production</th>
<th>Current Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{flags?.map(flag => (
<tr key={flag.key}>
<td>{flag.name}</td>
<td>
<input
type="checkbox"
checked={flag.isEnabledInDevelopment}
onChange={(e) => updateFlag.mutate({
key: flag.key,
updates: { isEnabledInDevelopment: e.target.checked }
})}
/>
</td>
<td>
<input
type="checkbox"
checked={flag.isEnabledInStaging}
onChange={(e) => updateFlag.mutate({
key: flag.key,
updates: { isEnabledInStaging: e.target.checked }
})}
/>
</td>
<td>
<input
type="checkbox"
checked={flag.isEnabledInProduction}
onChange={(e) => updateFlag.mutate({
key: flag.key,
updates: { isEnabledInProduction: e.target.checked }
})}
/>
</td>
<td>
<Badge color={flag.isEnabledInCurrentEnvironment ? 'green' : 'red'}>
{flag.isEnabledInCurrentEnvironment ? 'Enabled' : 'Disabled'}
</Badge>
</td>
<td>
<Button onClick={() => {/* Edit */}}>Edit</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}

Best Practices​

1. Cache Feature Flags​

  • Cache flags for 5 minutes (matches backend cache)
  • Refetch on app start
  • Invalidate cache when admin updates flags

2. Graceful Degradation​

  • Show fallback UI when feature is disabled
  • Don't break the app if flags fail to load
  • Use default values (false) if flags unavailable

3. Type Safety​

// types/features.ts
export type FeatureKey =
| 'Circles'
| 'Tournaments'
| 'Charters'
| 'Events';

export interface FeatureFlags {
Circles: boolean;
Tournaments: boolean;
Charters: boolean;
Events: boolean;
}

4. Error Handling​

export function useFeatureFlag(featureKey: string) {
const { data: flags, error, isLoading } = useFeatureFlags();

if (error) {
console.error('Failed to load feature flags:', error);
// Default to false for safety
return false;
}

if (isLoading) {
// Return false while loading to prevent flash of content
return false;
}

return flags?.[featureKey] ?? false;
}

5. Testing​

// Mock feature flags in tests
jest.mock('@/hooks/useFeatureFlags', () => ({
useFeatureFlag: jest.fn((key: string) => {
const mockFlags: Record<string, boolean> = {
Circles: true,
Tournaments: false,
};
return mockFlags[key] ?? false;
})
}));

UI Components​

Feature Badge​

// Show badge when feature is in beta/testing
<FeatureGate feature="Circles">
<Badge color="blue">Beta</Badge>
</FeatureGate>

Coming Soon Message​

<FeatureGate 
feature="Circles"
fallback={
<div className="coming-soon">
<h3>Coming Soon</h3>
<p>This feature will be available soon!</p>
</div>
}
>
<CirclesContent />
</FeatureGate>

Common Feature Flags​

Based on the backend implementation:

  • Circles - Social circles feature (enabled in dev/staging, disabled in prod)
  • Tournaments - Tournament management
  • Charters - Charter booking system
  • Events - Fishing events and expos
  • Advertising - Advertiser platform

Environment Detection​

The backend automatically detects the environment. Frontend can also check:

const environment = process.env.NODE_ENV === 'production' 
? 'Production'
: process.env.NEXT_PUBLIC_ENV === 'staging'
? 'Staging'
: 'Development';

Resources​

  • Backend API: /api/admin/feature-flags
  • Backend Documentation: docs/features/feature-flags.md
  • Feature Flag Service: Backend caches flags for 5 minutes