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.