Feature Flag Helpers
Feature flag management utilities for checking and managing feature availability in the WorkPayCore frontend application.
Core Function
isFeatureFlagEnabled(featureFlags)
Checks if one or more feature flags are enabled for the current company.
Parameters:
featureFlags(TFeatFlagValues | TFeatFlagValues[]): Single feature flag or array of feature flags to check
Returns:
boolean: True if the feature flag(s) are enabled
Example:
import { isFeatureFlagEnabled } from '@/utils/helpers/feature-flag';
import { FEATURE_FLAGS } from '@/utils/constants/feature-flags';
// Check single feature flag
const isTimeAttendanceV2Enabled = isFeatureFlagEnabled(FEATURE_FLAGS.TA_V2);
if (isTimeAttendanceV2Enabled) {
// Show new time attendance interface
return <TimeAttendanceV2 />;
}
// Check multiple feature flags (returns true if ANY are enabled)
const hasPaymentFeatures = isFeatureFlagEnabled([
FEATURE_FLAGS.PAYMENTS_V2,
FEATURE_FLAGS.wallet_pin_reset,
]);
if (hasPaymentFeatures) {
// Show payment features
return <PaymentModule />;
}
Use Cases:
- Conditional component rendering
- Feature availability checking
- A/B testing implementation
- Gradual feature rollout
Integration Patterns
React Component Wrapper
import { isFeatureFlagEnabled } from '@/utils/helpers/feature-flag';
import { FEATURE_FLAGS } from '@/utils/constants/feature-flags';
interface FeatureGateProps {
flag: string;
children: React.ReactNode;
fallback?: React.ReactNode;
}
const FeatureGate: React.FC<FeatureGateProps> = ({
flag,
children,
fallback = null,
}) => {
const isEnabled = isFeatureFlagEnabled(flag);
return isEnabled ? <>{children}</> : <>{fallback}</>;
};
// Usage
const PayrollInterface = () => (
<div>
<h1>Payroll</h1>
<FeatureGate
flag={FEATURE_FLAGS['revamped-run-payroll-interface']}
fallback={<LegacyPayrollInterface />}
>
<NewPayrollInterface />
</FeatureGate>
</div>
);
Custom Hook
import { isFeatureFlagEnabled } from '@/utils/helpers/feature-flag';
const useFeatureFlag = (flag: string) => {
const [isEnabled, setIsEnabled] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
try {
const enabled = isFeatureFlagEnabled(flag);
setIsEnabled(enabled);
} catch (error) {
console.error('Feature flag check failed:', error);
setIsEnabled(false);
} finally {
setLoading(false);
}
}, [flag]);
return { isEnabled, loading };
};
// Usage
const PaymentComponent = () => {
const { isEnabled: isPaymentsV2Enabled, loading } = useFeatureFlag(
FEATURE_FLAGS.PAYMENTS_V2,
);
if (loading) return <Spinner />;
return isPaymentsV2Enabled ? <PaymentsV2 /> : <LegacyPayments />;
};
Multiple Flag Checking
import { isFeatureFlagEnabled } from '@/utils/helpers/feature-flag';
import { FEATURE_FLAGS } from '@/utils/constants/feature-flags';
const useMultipleFeatureFlags = (flags: string[]) => {
const [enabledFlags, setEnabledFlags] = useState<Record<string, boolean>>({});
useEffect(() => {
const flagStates = flags.reduce(
(acc, flag) => {
acc[flag] = isFeatureFlagEnabled(flag);
return acc;
},
{} as Record<string, boolean>,
);
setEnabledFlags(flagStates);
}, [flags]);
return enabledFlags;
};
// Usage
const AdminDashboard = () => {
const flagStates = useMultipleFeatureFlags([
FEATURE_FLAGS.employee_expense_report,
FEATURE_FLAGS.multi_category_expense,
FEATURE_FLAGS.revamped_payslip,
]);
return (
<div>
<h1>Admin Dashboard</h1>
{flagStates[FEATURE_FLAGS.employee_expense_report] && (
<EmployeeExpenseReportWidget />
)}
{flagStates[FEATURE_FLAGS.multi_category_expense] && (
<MultiCategoryExpenseWidget />
)}
{flagStates[FEATURE_FLAGS.revamped_payslip] && <NewPayslipPreview />}
</div>
);
};
Advanced Usage Examples
Navigation Based on Feature Flags
import { isFeatureFlagEnabled } from '@/utils/helpers/feature-flag';
import { FEATURE_FLAGS } from '@/utils/constants/feature-flags';
const useFeatureBasedRoutes = () => {
const routes = useMemo(() => {
const baseRoutes = [
{ path: '/dashboard', component: Dashboard },
{ path: '/employees', component: Employees },
];
// Conditionally add routes based on feature flags
if (isFeatureFlagEnabled(FEATURE_FLAGS.TA_V2)) {
baseRoutes.push({
path: '/time-attendance',
component: TimeAttendanceV2,
});
} else {
baseRoutes.push({
path: '/time-attendance',
component: LegacyTimeAttendance,
});
}
if (isFeatureFlagEnabled(FEATURE_FLAGS.PAYMENTS_V2)) {
baseRoutes.push({
path: '/payments',
component: PaymentsV2,
});
}
return baseRoutes;
}, []);
return routes;
};
Feature Flag Middleware
import { isFeatureFlagEnabled } from '@/utils/helpers/feature-flag';
const createFeatureFlagMiddleware = (
flag: string,
fallbackComponent: React.ComponentType,
) => {
return (WrappedComponent: React.ComponentType) => {
const WithFeatureFlag = (props: any) => {
const isEnabled = isFeatureFlagEnabled(flag);
if (!isEnabled) {
return <fallbackComponent {...props} />;
}
return <WrappedComponent {...props} />;
};
WithFeatureFlag.displayName = `WithFeatureFlag(${
WrappedComponent.displayName || WrappedComponent.name
})`;
return WithFeatureFlag;
};
};
// Usage
const withPaymentsV2 = createFeatureFlagMiddleware(
FEATURE_FLAGS.PAYMENTS_V2,
LegacyPayments,
);
const PaymentsPage = withPaymentsV2(PaymentsV2);
API Integration
import { isFeatureFlagEnabled } from '@/utils/helpers/feature-flag';
import { FEATURE_FLAGS } from '@/utils/constants/feature-flags';
const createApiClient = () => {
const baseConfig = {
baseURL: '/api/v1',
headers: {
'Content-Type': 'application/json',
},
};
// Add feature flag headers for backend
const enabledFlags = Object.values(FEATURE_FLAGS).filter(flag =>
isFeatureFlagEnabled(flag),
);
if (enabledFlags.length > 0) {
baseConfig.headers['X-Feature-Flags'] = enabledFlags.join(',');
}
return axios.create(baseConfig);
};
// Usage
const apiClient = createApiClient();
// The backend can now adjust responses based on enabled feature flags
const fetchUserData = () => apiClient.get('/users');
Form Field Conditional Rendering
import { isFeatureFlagEnabled } from '@/utils/helpers/feature-flag';
import { FEATURE_FLAGS } from '@/utils/constants/feature-flags';
const ExpenseForm = () => {
const isMultiCategoryEnabled = isFeatureFlagEnabled(
FEATURE_FLAGS.multi_category_expense,
);
return (
<form>
<input name='description' placeholder='Description' />
<input name='amount' type='number' placeholder='Amount' />
{isMultiCategoryEnabled ? (
<MultiCategorySelect name='categories' />
) : (
<SingleCategorySelect name='category' />
)}
<button type='submit'>Submit Expense</button>
</form>
);
};
Analytics Integration
import { isFeatureFlagEnabled } from '@/utils/helpers/feature-flag';
import { analyticsTrackEvent } from '@/utils/helpers/analytics';
const trackFeatureUsage = (feature: string, action: string) => {
if (isFeatureFlagEnabled(feature)) {
analyticsTrackEvent('feature_usage', {
feature,
action,
enabled: true,
timestamp: new Date().toISOString(),
});
}
};
// Usage
const PayrollComponent = () => {
const handleRunPayroll = () => {
trackFeatureUsage(
FEATURE_FLAGS['revamped-run-payroll-interface'],
'run_payroll_clicked',
);
// Run payroll logic
};
return <button onClick={handleRunPayroll}>Run Payroll</button>;
};
Error Handling
Safe Feature Flag Checking
import { isFeatureFlagEnabled } from '@/utils/helpers/feature-flag';
const safeFeatureFlagCheck = (flag: string, defaultValue = false) => {
try {
return isFeatureFlagEnabled(flag);
} catch (error) {
console.error(`Feature flag check failed for ${flag}:`, error);
return defaultValue;
}
};
// Usage with fallback
const ExpenseReporting = () => {
const hasEmployeeReports = safeFeatureFlagCheck(
FEATURE_FLAGS.employee_expense_report,
false, // Default to disabled if check fails
);
return (
<div>
<h2>Expense Reports</h2>
{hasEmployeeReports && <EmployeeExpenseButton />}
</div>
);
};
Data Flow
How Feature Flags Work
- Query Client: Uses React Query to fetch enabled features from API
- Cache: Enabled features are cached in memory via
queryClient - Check Function:
isFeatureFlagEnabledreads from the cache - Array Support: Can check single flag or array of flags
- Boolean Logic: Returns true if ANY flag in array is enabled
Data Structure
// Example cached data structure
const cachedFeatureFlags = {
data: {
data: [
{ id: 1, code: 'revamped-time-and-attendance', enabled: true },
{ id: 2, code: 'revamped-payouts-module', enabled: false },
{ id: 3, code: 'wallet-pin-reset', enabled: true },
],
},
};
Performance Considerations
- Caching: Feature flags are cached to avoid repeated API calls
- Synchronous: Flag checking is synchronous once data is loaded
- Memory: Minimal memory footprint with simple array operations
- Re-renders: Use useMemo for expensive flag combinations
Best Practices
- Default Behavior: Always provide fallback for disabled features
- Error Handling: Wrap flag checks in try-catch blocks
- Testing: Test both enabled and disabled states
- Documentation: Document which flags affect which features
- Cleanup: Remove flag checks after full rollout
Related Utilities
- Feature Flag Constants - For flag definitions
- Analytics Helpers - For tracking feature usage
- General Helpers - For common utilities
TypeScript Definitions
import { TFeatFlagValues } from '@/utils/constants/feature-flags';
export function isFeatureFlagEnabled(
featureFlags: TFeatFlagValues | TFeatFlagValues[],
): boolean;
Dependencies
- React Query: For caching feature flag data
- Feature Flag API: Backend service for flag management
- Feature Flag Constants: For type-safe flag references