Skip to main content

Best Practices

Comprehensive guide to best practices for using WorkPayCore Frontend utilities effectively and maintaining high code quality.

Overview​

This guide outlines recommended practices for implementing, using, and maintaining the WorkPayCore Frontend utilities. Following these practices ensures consistency, performance, and maintainability across the codebase.

General Development Principles​

1. Type Safety First​

Always use TypeScript with proper type definitions:

// ✅ Good: Explicit types
interface UserData {
id: number;
name: string;
email: string;
}

const processUser = (user: UserData): string => {
return formatUserDisplayName(user.name);
};

// ❌ Avoid: Any types
const processUser = (user: any) => {
return user.name;
};

2. Error Handling​

Implement comprehensive error handling:

// ✅ Good: Graceful error handling
import { validatePhoneNumber } from '@/utils/helpers/phone-number';

const processPhoneNumber = (phone: string, country: string) => {
try {
const validation = validatePhoneNumber(phone, country);
if (!validation.isValid) {
console.warn('Invalid phone number:', validation.error);
return null;
}
return validation;
} catch (error) {
console.error('Phone validation failed:', error);
return null;
}
};

// ❌ Avoid: Unhandled errors
const processPhoneNumber = (phone: string, country: string) => {
return validatePhoneNumber(phone, country);
};

3. Performance Optimization​

Use utilities efficiently:

// ✅ Good: Memoized calculations
import { useMemo } from 'react';
import { getTotalAmount } from '@/utils/core-utils/number-utils';

const ExpensesSummary = ({ expenses }) => {
const totalAmount = useMemo(
() => getTotalAmount(expenses, 'amount'),
[expenses],
);

return <div>Total: {totalAmount}</div>;
};

// ❌ Avoid: Repeated calculations
const ExpensesSummary = ({ expenses }) => {
return <div>Total: {getTotalAmount(expenses, 'amount')}</div>; // Recalculates on every render
};

Utility-Specific Best Practices​

String Utilities​

import {
isNullOrEmpty,
capitalizeFirstLetter,
} from '@/utils/core-utils/string-utils';

// ✅ Good: Validate before processing
const formatUserName = (firstName?: string, lastName?: string) => {
if (isNullOrEmpty(firstName) && isNullOrEmpty(lastName)) {
return 'Anonymous User';
}

const first = firstName ? capitalizeFirstLetter(firstName) : '';
const last = lastName ? capitalizeFirstLetter(lastName) : '';

return `${first} ${last}`.trim();
};

// ❌ Avoid: Assuming data exists
const formatUserName = (firstName: string, lastName: string) => {
return `${firstName} ${lastName}`;
};

Date & Time Utilities​

import {
isValidDateString,
formatDateTime,
} from '@/utils/core-utils/date-time-utils';

// ✅ Good: Validate dates before formatting
const DisplayDate = ({ dateString }) => {
if (!isValidDateString(dateString)) {
return <span>Invalid Date</span>;
}

const formatted = formatDateTime(dateString);
return <span>{formatted.formattedDate}</span>;
};

// ❌ Avoid: Direct date formatting without validation
const DisplayDate = ({ dateString }) => {
return <span>{formatDateTime(dateString).formattedDate}</span>;
};

HTTP Requests (Axios)​

import { httpV2 } from '@/api/axios';

// ✅ Good: Proper error handling and types
interface ApiResponse&lt;T&gt; {
data: T;
message: string;
status: 'success' | 'error';
}

const fetchUserData = async (userId: number): Promise<User | null> => {
try {
const response = await httpV2.get<ApiResponse&lt;User&gt;>(`/users/${userId}`);
return response.data.data;
} catch (error) {
if (error.response?.status === 404) {
console.warn('User not found:', userId);
return null;
}
console.error('Failed to fetch user:', error);
throw error;
}
};

// ❌ Avoid: No error handling
const fetchUserData = async (userId: number) => {
const response = await httpV2.get(`/users/${userId}`);
return response.data;
};

Feature Flags​

import { isFeatureFlagEnabled } from '@/utils/helpers/feature-flag-helpers';
import { FEATURE_FLAGS } from '@/utils/constants/feature-flags';

// ✅ Good: Graceful feature flag handling
const PayrollComponent = () => {
const [isNewPayrollEnabled, setIsNewPayrollEnabled] = useState(false);

useEffect(() => {
const checkFlag = async () => {
try {
const enabled = await isFeatureFlagEnabled(FEATURE_FLAGS.PAYMENTS_V2);
setIsNewPayrollEnabled(enabled);
} catch (error) {
console.error('Feature flag check failed:', error);
setIsNewPayrollEnabled(false); // Fail to safe state
}
};

checkFlag();
}, []);

return isNewPayrollEnabled ? <NewPayrollUI /> : <LegacyPayrollUI />;
};

// ❌ Avoid: Synchronous feature flag usage
const PayrollComponent = () => {
const isEnabled = isFeatureFlagEnabled(FEATURE_FLAGS.PAYMENTS_V2); // Async function used synchronously
return isEnabled ? <NewPayrollUI /> : <LegacyPayrollUI />;
};

React Integration Patterns​

Custom Hooks for Utilities​

// ✅ Good: Create reusable hooks
import { useState, useEffect } from 'react';
import { validatePhoneNumber } from '@/utils/helpers/phone-number';

const usePhoneValidation = (phone: string, country: string) => {
const [validation, setValidation] = useState(null);
const [isLoading, setIsLoading] = useState(false);

useEffect(() => {
if (!phone) {
setValidation(null);
return;
}

setIsLoading(true);

try {
const result = validatePhoneNumber(phone, country);
setValidation(result);
} catch (error) {
setValidation({ isValid: false, error: error.message });
} finally {
setIsLoading(false);
}
}, [phone, country]);

return { validation, isLoading };
};

// Usage in component
const PhoneInput = () => {
const [phone, setPhone] = useState('');
const { validation, isLoading } = usePhoneValidation(phone, 'US');

return (
<div>
<input value={phone} onChange={e => setPhone(e.target.value)} />
{isLoading && <span>Validating...</span>}
{validation && !validation.isValid && (
<span className='error'>{validation.error}</span>
)}
</div>
);
};

Context for Shared Utilities​

// ✅ Good: Use context for shared utility data
import { createContext, useContext, useState, useEffect } from 'react';
import { getAllCountries } from '@/utils/helpers/country-helpers';

interface UtilityContextType {
countries: Country[];
isLoading: boolean;
}

const UtilityContext = createContext&lt;UtilityContextType&gt;({
countries: [],
isLoading: true,
});

export const UtilityProvider = ({ children }) => {
const [countries, setCountries] = useState([]);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
const loadData = async () => {
try {
const countryData = await getAllCountries();
setCountries(countryData);
} catch (error) {
console.error('Failed to load countries:', error);
} finally {
setIsLoading(false);
}
};

loadData();
}, []);

return (
<UtilityContext.Provider value={{ countries, isLoading }}>
{children}
</UtilityContext.Provider>
);
};

export const useUtilities = () => useContext(UtilityContext);

Code Organization​

Import Organization​

// ✅ Good: Organized imports
// React and third-party libraries
import React, { useState, useEffect } from 'react';
import { format } from 'date-fns';

// Internal utilities (grouped by category)
import { httpV2 } from '@/api/axios';
import { formatCurrency } from '@/utils/helpers/country-helpers';
import { isValidDateString } from '@/utils/core-utils/date-time-utils';
import { getTotalAmount } from '@/utils/core-utils/number-utils';

// Types
import type { User, PayrollData } from '@/types';

// ❌ Avoid: Random import order
import { getTotalAmount } from '@/utils/core-utils/number-utils';
import React from 'react';
import { httpV2 } from '@/api/axios';
import type { User } from '@/types';
import { useState } from 'react';

Utility Combination​

// ✅ Good: Combine related utilities
import {
removeFalsy,
isLiteralObject,
} from '@/utils/core-utils/object-array-utils';
import {
isNullOrEmpty,
capitalizeFirstLetter,
} from '@/utils/core-utils/string-utils';

const cleanAndFormatData = (rawData: any) => {
// Validate input
if (!isLiteralObject(rawData)) {
return null;
}

// Clean data
const cleanedData = removeFalsy(rawData);

// Format strings
const formatted = Object.entries(cleanedData).reduce((acc, [key, value]) => {
if (typeof value === 'string' && !isNullOrEmpty(value)) {
acc[key] = capitalizeFirstLetter(value);
} else {
acc[key] = value;
}
return acc;
}, {});

return formatted;
};

Testing Best Practices​

Unit Testing Utilities​

// ✅ Good: Comprehensive utility testing
import { validatePhoneNumber } from '@/utils/helpers/phone-number';

describe('validatePhoneNumber', () => {
it('should validate US phone numbers correctly', () => {
const result = validatePhoneNumber('+1234567890', 'US');
expect(result.isValid).toBe(true);
expect(result.country).toBe('US');
});

it('should handle invalid phone numbers gracefully', () => {
const result = validatePhoneNumber('invalid', 'US');
expect(result.isValid).toBe(false);
expect(result.error).toBeDefined();
});

it('should handle missing country code', () => {
const result = validatePhoneNumber('+1234567890');
expect(result.isValid).toBe(true);
});

it('should handle edge cases', () => {
expect(() => validatePhoneNumber(null, 'US')).not.toThrow();
expect(() => validatePhoneNumber('', 'US')).not.toThrow();
});
});

Integration Testing​

// ✅ Good: Test utility integration with components
import { render, screen, waitFor } from '@testing-library/react';
import { PhoneInput } from './PhoneInput';

// Mock the utility
jest.mock('@/utils/helpers/phone-number', () => ({
validatePhoneNumber: jest.fn(),
}));

describe('PhoneInput Component', () => {
it('should show validation error for invalid phone', async () => {
const mockValidate =
require('@/utils/helpers/phone-number').validatePhoneNumber;
mockValidate.mockReturnValue({ isValid: false, error: 'Invalid phone' });

render(<PhoneInput />);

const input = screen.getByRole('textbox');
fireEvent.change(input, { target: { value: 'invalid' } });

await waitFor(() => {
expect(screen.getByText('Invalid phone')).toBeInTheDocument();
});
});
});

Performance Guidelines​

1. Memoization​

// ✅ Good: Memoize expensive calculations
const PayrollSummary = ({ payrollData }) => {
const totalSalaries = useMemo(
() => getTotalAmount(payrollData, 'salary'),
[payrollData],
);

const totalBenefits = useMemo(
() => getTotalAmount(payrollData, 'benefits'),
[payrollData],
);

return (
<div>
<div>Salaries: {totalSalaries}</div>
<div>Benefits: {totalBenefits}</div>
</div>
);
};

2. Debouncing​

// ✅ Good: Debounce user input validation
import { debounceFunction } from '@/utils/helpers/general-helpers';

const SearchInput = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);

const debouncedSearch = useMemo(
() =>
debounceFunction(async searchQuery => {
const searchResults = await searchAPI(searchQuery);
setResults(searchResults);
}, 300),
[],
);

useEffect(() => {
if (query) {
debouncedSearch(query);
} else {
setResults([]);
}
}, [query, debouncedSearch]);

return (
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder='Search...'
/>
);
};

3. Lazy Loading​

// ✅ Good: Lazy load utilities when needed
const HeavyFeature = lazy(() => import('./HeavyFeature'));

const App = () => {
const [showHeavyFeature, setShowHeavyFeature] = useState(false);

return (
<div>
<button onClick={() => setShowHeavyFeature(true)}>
Load Heavy Feature
</button>

{showHeavyFeature && (
<Suspense fallback={<div>Loading...</div>}>
<HeavyFeature />
</Suspense>
)}
</div>
);
};

Security Best Practices​

Data Encryption​

// ✅ Good: Conditional encryption with fallback
import { encryptData } from '@/utils/helpers/encryption-helpers';

const secureSubmit = async sensitiveData => {
try {
// Try to encrypt if possible
const payload = await encryptData(sensitiveData);
return submitData(payload);
} catch (encryptionError) {
console.error('Encryption failed:', encryptionError);

// In production, fail securely
if (process.env.NODE_ENV === 'production') {
throw new Error('Secure submission required but encryption failed');
}

// In development, warn and proceed
console.warn('Proceeding without encryption in development');
return submitData(sensitiveData);
}
};

Input Validation​

// ✅ Good: Validate all user inputs
import { isNull } from '@/utils/helpers/general-helpers';
import { validatePhoneNumber } from '@/utils/helpers/phone-number';

const validateUserProfile = profile => {
const errors = {};

if (isNull(profile.firstName)) {
errors.firstName = 'First name is required';
}

if (isNull(profile.email)) {
errors.email = 'Email is required';
} else if (!isValidEmail(profile.email)) {
errors.email = 'Email format is invalid';
}

if (profile.phone) {
const phoneValidation = validatePhoneNumber(profile.phone, profile.country);
if (!phoneValidation.isValid) {
errors.phone = phoneValidation.error;
}
}

return {
isValid: Object.keys(errors).length === 0,
errors,
};
};

Documentation Standards​

Function Documentation​

/**
* Calculates the total amount from an array of objects
*
* @param items - Array of objects containing numeric values
* @param key - The property key to sum
* @returns The total sum of all values for the specified key
*
* @example
* ```typescript
* const expenses = [
* { amount: 100, category: 'food' },
* { amount: 50, category: 'transport' }
* ];
* const total = getTotalAmount(expenses, 'amount'); // Returns: 150
* ```
*/
export const getTotalAmount = (items: any[], key: string): number => {
// Implementation...
};

Component Documentation​

/**
* PhoneInput Component
*
* A controlled input component with international phone number validation
*
* @param props.value - Current phone number value
* @param props.country - Country code for validation context
* @param props.onChange - Callback when phone number changes
* @param props.onValidation - Callback when validation state changes
*
* @example
* ```tsx
* <PhoneInput
* value={phone}
* country="US"
* onChange={setPhone}
* onValidation={setValidation}
* />
* ```
*/
const PhoneInput: React.FC&lt;PhoneInputProps&gt; = ({ ... }) => {
// Implementation...
};

Checklist for Code Reviews​

General​

  • TypeScript types are properly defined
  • Error handling is comprehensive
  • Performance considerations are addressed
  • Security best practices are followed

Utility Usage​

  • Appropriate utilities are used for the task
  • Input validation is performed
  • Edge cases are handled
  • Performance optimizations (memoization, debouncing) are applied where needed

Documentation​

  • Functions are properly documented
  • Examples are provided for complex usage
  • TypeScript definitions are exported
  • Related utilities are cross-referenced

Testing​

  • Unit tests cover main functionality
  • Edge cases are tested
  • Error conditions are tested
  • Integration with components is tested

Following these best practices ensures that your code is maintainable, performant, and follows WorkPayCore Frontend standards.