Skip to main content

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&lt;FeatureGateProps&gt; = ({
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

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

  1. Query Client: Uses React Query to fetch enabled features from API
  2. Cache: Enabled features are cached in memory via queryClient
  3. Check Function: isFeatureFlagEnabled reads from the cache
  4. Array Support: Can check single flag or array of flags
  5. 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

  1. Default Behavior: Always provide fallback for disabled features
  2. Error Handling: Wrap flag checks in try-catch blocks
  3. Testing: Test both enabled and disabled states
  4. Documentation: Document which flags affect which features
  5. Cleanup: Remove flag checks after full rollout


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