Utils Documentation
Overview
The utils directory (src/utils/) contains pure utility functions that provide common functionality across the application. These are stateless, reusable functions for data transformation, validation, formatting, and other shared operations.
Directory Structure
src/utils/
├── checkers.ts # Validation checkers
├── cn.ts # Tailwind class merging
├── create-css-vars.ts # CSS variable generation
├── create-number-array.ts # Number array utilities
├── empty-array-checker.ts # Array validation
├── error-handler.ts # Error handling utilities
├── excel-format-date.ts # Date formatting for Excel
├── first-item-value.ts # Array accessors
├── format-data.ts # Data formatting
├── format-name.ts # Name formatting
├── format-number.ts # Number formatting
├── get-navigation-type.ts # Navigation detection
├── get-server-session.ts # Server session helper
├── get-session-id.ts # Session ID extraction
├── get-user-type-from-session.ts # User type detection
├── mapped-data.ts # Data mapping utilities
├── parse-number-from-string.ts # Number parsing
├── pick-random-item.ts # Random selection
├── provider/ # Provider utilities (1 item)
├── refiner.ts # Data refinement
├── row-selector.ts # Table row selection
├── signout-falsy.ts # Signout helpers
├── success-message.ts # Success messaging
├── to-api-date.ts # Date API formatting
├── type-utils.ts # TypeScript utilities
└── with-idx.ts # Index utilitiesCore Utilities
Class Name Merging (cn.ts)
Tailwind CSS class merging utility using tailwind-merge and clsx.
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}Usage:
import { cn } from '@/utils/cn';
// Conditional classes
<div className={cn('base-class', isActive && 'active-class')}>
// Override classes
<Button className={cn('btn-base', className)}>
// Merge conflicting Tailwind classes
<div className={cn('px-4 py-2', 'p-6')}> // Results in 'p-6'Error Handling (error-handler.ts)
Centralized error handling utilities.
export function handleApiError(error: unknown): ApiError;
export function handleFormError(error: unknown): FormError;
export function getErrorMessage(error: unknown): string;
export function isNotFoundError(error: unknown): boolean;
export function isForbiddenError(error: unknown): boolean;Usage:
import { handleApiError, getErrorMessage } from '@/utils/error-handler';
try {
await apiCall();
} catch (error) {
const apiError = handleApiError(error);
toast.error(getErrorMessage(error));
}Formatting Utilities
Name Formatting (format-name.ts)
export function formatFullName(firstName: string, lastName: string): string;
export function formatInitials(firstName: string, lastName: string): string;
export function formatLastNameFirst(firstName: string, lastName: string): string;Usage:
import { formatFullName, formatInitials } from '@/utils/format-name';
formatFullName('John', 'Doe'); // 'John Doe'
formatInitials('John', 'Doe'); // 'JD'
formatLastNameFirst('John', 'Doe'); // 'Doe, John'Number Formatting (format-number.ts)
export function formatCurrency(amount: number, currency?: string): string;
export function formatPercentage(value: number, decimals?: number): string;
export function formatCompactNumber(value: number): string;Usage:
import { formatCurrency, formatPercentage } from '@/utils/format-number';
formatCurrency(1234.56); // '$1,234.56'
formatPercentage(0.1234, 1); // '12.3%'
formatCompactNumber(1234567); // '1.2M'Date Formatting for Excel (excel-format-date.ts)
export function formatDateForExcel(date: Date): string;
export function parseExcelDate(value: string | number): Date;Data Utilities
Data Mapping (mapped-data.ts)
Transform and map data structures:
export function mapArrayToObject<T>(
array: T[],
keyExtractor: (item: T) => string
): Record<string, T>;
export function groupBy<T>(
array: T[],
keyExtractor: (item: T) => string
): Record<string, T[]>;Usage:
import { mapArrayToObject, groupBy } from '@/utils/mapped-data';
const membersById = mapArrayToObject(members, m => m.id);
// { '1': { id: '1', name: 'John' }, ... }
const membersByProvider = groupBy(members, m => m.insuranceProvider);
// { 'Cocolife': [...], 'Maxicare': [...] }Data Refinement (refiner.ts)
Clean and refine data:
export function refineString(value: unknown): string | null;
export function refineNumber(value: unknown): number | null;
export function refineDate(value: unknown): Date | null;
export function refineBoolean(value: unknown): boolean | null;
export function removeNullValues<T extends Record<string, unknown>>(obj: T): Partial<T>;Data Formatting (format-data.ts)
Format various data types for display:
export function formatPhoneNumber(phone: string): string;
export function formatAddress(address: Address): string;
export function formatMemberId(id: string, provider: string): string;Array Utilities
Empty Array Checker (empty-array-checker.ts)
export function isEmptyArray<T>(value: T[] | null | undefined): value is [];
export function hasItems<T>(value: T[] | null | undefined): value is [T, ...T[]];Usage:
import { isEmptyArray, hasItems } from '@/utils/empty-array-checker';
if (isEmptyArray(members)) {
return <EmptyState />;
}
if (hasItems(members)) {
return <MemberList members={members} />; // TypeScript knows array is non-empty
}Number Array Creator (create-number-array.ts)
export function createNumberArray(length: number, startAt?: number): number[];Usage:
import { createNumberArray } from '@/utils/create-number-array';
createNumberArray(5); // [0, 1, 2, 3, 4]
createNumberArray(5, 1); // [1, 2, 3, 4, 5]First Item Value (first-item-value.ts)
export function getFirstItem<T>(array: T[]): T | undefined;
export function getFirstItemOrDefault<T>(array: T[], defaultValue: T): T;Random Item Picker (pick-random-item.ts)
export function pickRandomItem<T>(array: T[]): T;
export function pickRandomItems<T>(array: T[], count: number): T[];TypeScript Utilities (type-utils.ts)
Type-level utilities for TypeScript:
export type Nullable<T> = T | null;
export type Optional<T> = T | undefined;
export type NonEmptyArray<T> = [T, ...T[]];
export type ArrayElement<A> = A extends readonly (infer T)[] ? T : never;
export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};Session Utilities
Server Session (get-server-session.ts)
Next.js server-side session retrieval:
import { type GetServerSidePropsContext } from 'next';
export async function getServerSession(ctx: GetServerSidePropsContext) {
// Returns session or null
}Usage in getServerSideProps:
import { getServerSession } from '@/utils/get-server-session';
export const getServerSideProps = async (ctx) => {
const session = await getServerSession(ctx);
if (!session) {
return { redirect: { destination: '/' } };
}
return { props: { user: session.user } };
};Session ID (get-session-id.ts)
Extract session identifier:
export function getSessionId(): string | null;
export function generateSessionId(): string;User Type from Session (get-user-type-from-session.ts)
export function getUserTypeFromSession(session: Session): UserType;
export function isBroker(session: Session): boolean;
export function isPrincipal(session: Session): boolean;
export function isBeneficiary(session: Session): boolean;Navigation Utilities
Navigation Type (get-navigation-type.ts)
Detect navigation method:
export function getNavigationType(): 'reload' | 'navigate' | 'back_forward';
export function isPageReload(): boolean;
export function isBackNavigation(): boolean;Date Utilities
API Date Formatting (to-api-date.ts)
Convert dates to API format:
export function toApiDate(date: Date | string): string; // ISO format
export function fromApiDate(dateString: string): Date;
export function isValidApiDate(value: string): boolean;Usage:
import { toApiDate, fromApiDate } from '@/utils/to-api-date';
const apiDate = toApiDate(new Date()); // '2024-01-15T10:30:00.000Z'
const date = fromApiDate('2024-01-15'); // Date objectTable Utilities
Row Selector (row-selector.ts)
Table row selection utilities:
export function toggleRowSelection<T>(
selectedRows: T[],
row: T,
identifier: (item: T) => string
): T[];
export function selectAllRows<T>(
rows: T[],
selectedRows: T[],
identifier: (item: T) => string
): T[];
export function isRowSelected<T>(
selectedRows: T[],
row: T,
identifier: (item: T) => string
): boolean;Usage:
import { toggleRowSelection, isRowSelected } from '@/utils/row-selector';
const [selected, setSelected] = useState<Member[]>([]);
const handleSelect = (member: Member) => {
setSelected(prev => toggleRowSelection(prev, member, m => m.id));
};
const isSelected = isRowSelected(selected, member, m => m.id);CSS Variable Utilities
CSS Variables Creator (create-css-vars.ts)
Generate CSS custom properties:
export function createCSSVars(variables: Record<string, string>): string;
export function createHSLVars(colors: Record<string, [number, number, number]>): string;Usage:
import { createCSSVars, createHSLVars } from '@/utils/create-css-vars';
const style = createCSSVars({
'--primary-color': '#007bff',
'--spacing-unit': '8px',
});
// Returns: '--primary-color: #007bff; --spacing-unit: 8px;'
const hslStyle = createHSLVars({
primary: [220, 90, 56],
});
// Returns: '--primary: 220 90% 56%;'Checkers (checkers.ts)
Validation utilities:
export function isValidEmail(email: string): boolean;
export function isValidPhone(phone: string): boolean;
export function isValidMemberId(id: string): boolean;
export function isValidDate(date: unknown): date is Date;
export function isObject(value: unknown): value is Record<string, unknown>;Index Utilities (with-idx.ts)
Add indices to data:
export function withIdx<T>(array: T[]): Array<T & { idx: number }>;
export function withUniqueId<T>(array: T[]): Array<T & { uniqueId: string }>;Usage:
import { withIdx } from '@/utils/with-idx';
const membersWithIndex = withIdx(members);
// [{ ...member, idx: 0 }, { ...member, idx: 1 }, ...]Signout Utilities (signout-falsy.ts)
Handle signout edge cases:
export function shouldSignOut(error: unknown): boolean;
export function handleSignoutError(error: unknown): void;Success Messaging (success-message.ts)
Generate success messages:
export function getSuccessMessage(action: string, entity?: string): string;
export function getBatchSuccessMessage(count: number, action: string): string;Usage:
import { getSuccessMessage, getBatchSuccessMessage } from '@/utils/success-message';
getSuccessMessage('create', 'member'); // 'Member created successfully'
getBatchSuccessMessage(5, 'delete'); // '5 items deleted successfully'Provider Utilities (provider/)
Provider-specific utility functions.
provider/
└── (utility files for insurance providers)Creating New Utilities
Pattern for New Utilities
// utils/my-utility.ts
/**
* @description Brief description of what the utility does
* @param param1 - Description of first parameter
* @param param2 - Description of second parameter
* @returns Description of return value
*
* @example
* myUtility('input', { option: true });
* // Returns: 'processed output'
*/
export interface MyUtilityOptions {
option?: boolean;
fallback?: string;
}
export function myUtility(
input: string,
options: MyUtilityOptions = {}
): string {
const { option = false, fallback = '' } = options;
if (!input) return fallback;
// Implementation
return input.toUpperCase();
}Testing Utilities
// utils/__tests__/my-utility.test.ts
import { describe, it, expect } from 'vitest';
import { myUtility } from '../my-utility';
describe('myUtility', () => {
it('should handle valid input', () => {
expect(myUtility('hello')).toBe('HELLO');
});
it('should return fallback for empty input', () => {
expect(myUtility('', { fallback: 'default' })).toBe('default');
});
});Best Practices
1. Pure Functions
Utilities should be pure - same input always produces same output:
// Good
export function double(x: number): number {
return x * 2;
}
// Bad - has side effect
let multiplier = 2;
export function double(x: number): number {
return x * multiplier; // Result depends on external state
}2. Input Validation
Always validate inputs and handle edge cases:
export function safeDivide(a: number, b: number): number | null {
if (b === 0) return null;
return a / b;
}3. Type Safety
Use TypeScript to ensure type safety:
// Use type guards
export function isString(value: unknown): value is string {
return typeof value === 'string';
}
// Use generics for flexibility
export function first<T>(array: T[]): T | undefined {
return array[0];
}4. Documentation
Document all utilities with JSDoc:
/**
* Formats a member's full name with title
* @param member - The member object
* @param includeTitle - Whether to include title prefix
* @returns Formatted name string
*/
export function formatMemberName(
member: Member,
includeTitle = false
): string {
// Implementation
}5. No Business Logic
Keep utilities generic - no business-specific logic:
// Good - generic formatting
export function formatCurrency(amount: number): string;
// Bad - business logic in utility
export function getMemberPrice(member: Member): number; // Move to servicesImport Patterns
Barrel Export (Not Currently Used)
Consider adding utils/index.ts for cleaner imports:
// utils/index.ts
export { cn } from './cn';
export { formatFullName } from './format-name';
export { formatCurrency } from './format-number';
// ... more exports
// Usage
import { cn, formatFullName } from '@/utils';Current Pattern
Import utilities directly from their files:
import { cn } from '@/utils/cn';
import { formatFullName } from '@/utils/format-name';
import { isEmptyArray } from '@/utils/empty-array-checker';Related Documentation
- TypeScript Utility Types
- Lodash Documentation (for inspiration)
- Tailwind Merge