Skip to main content

Lodash Helpers

Handwritten Lodash function replacements for common object and array operations in the WorkPayCore frontend application.

Overview

The Lodash helpers provide lightweight implementations of popular Lodash functions to reduce bundle size while maintaining functionality.

Object Utilities

omit(object, paths)

Creates a new object with specified properties removed.

Parameters:

  • object (object): The source object
  • paths (array, optional): Array of property names to omit (default: [])

Returns:

  • object: New object without the omitted properties

Example:

import { omit } from '@/utils/helpers/lodash';

const user = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
password: 'secret123',
role: 'admin',
};

// Remove sensitive data
const publicUser = omit(user, ['password']);
console.log(publicUser);
// Returns: { id: 1, name: 'John Doe', email: 'john@example.com', role: 'admin' }

// Remove multiple properties
const basicUser = omit(user, ['password', 'role']);
console.log(basicUser);
// Returns: { id: 1, name: 'John Doe', email: 'john@example.com' }

// Handle null/undefined objects
const safeOmit = omit(null, ['property']);
console.log(safeOmit);
// Returns: {}

Use Cases:

  • Removing sensitive data from API responses
  • Creating clean data for frontend display
  • Preparing data for different user roles
  • Sanitizing objects before storage

Array Utilities

getFirstItemFromArray(items)

Safely gets the first item from an array or array-like value.

Parameters:

  • items (any): Array, single item, or any value to extract first item from

Returns:

  • any: The first item or undefined if empty

Example:

import { getFirstItemFromArray } from '@/utils/helpers/lodash';

// Regular array
getFirstItemFromArray([1, 2, 3]); // Returns: 1
getFirstItemFromArray(['a', 'b', 'c']); // Returns: 'a'

// Single item (not array)
getFirstItemFromArray('hello'); // Returns: 'hello'
getFirstItemFromArray(42); // Returns: 42

// Empty array
getFirstItemFromArray([]); // Returns: undefined

// Null/undefined
getFirstItemFromArray(null); // Returns: undefined
getFirstItemFromArray(undefined); // Returns: undefined

Use Cases:

  • Safe array access without errors
  • Handling both single items and arrays
  • Default value extraction
  • API response processing

uniqBy(array, iteratee)

Creates a duplicate-free version of an array based on a property or function.

Parameters:

  • array (array, optional): The array to inspect (default: [])
  • iteratee (string | function): Property name or function to determine uniqueness

Returns:

  • array: New array with unique values

Example:

import { uniqBy } from '@/utils/helpers/lodash';

// Using property name
const users = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
{ id: 1, name: 'John Duplicate' },
{ id: 3, name: 'Bob' },
];

const uniqueUsers = uniqBy(users, 'id');
console.log(uniqueUsers);
// Returns: [
// { id: 1, name: 'John' },
// { id: 2, name: 'Jane' },
// { id: 3, name: 'Bob' }
// ]

// Using function
const products = [
{ name: 'Laptop', category: 'Electronics', price: 1000 },
{ name: 'Mouse', category: 'Electronics', price: 25 },
{ name: 'Desk', category: 'Furniture', price: 300 },
{ name: 'Chair', category: 'Furniture', price: 200 },
];

const uniqueCategories = uniqBy(products, item => item.category);
console.log(uniqueCategories);
// Returns: [
// { name: 'Laptop', category: 'Electronics', price: 1000 },
// { name: 'Desk', category: 'Furniture', price: 300 }
// ]

// Handle empty arrays
const emptyResult = uniqBy([], 'id');
console.log(emptyResult);
// Returns: []

Use Cases:

  • Removing duplicate records
  • Unique user lists
  • Deduplicating API responses
  • Creating unique option lists

sortByKey(collection, key, order?)

Sorts an array of objects by a specified property.

Parameters:

  • collection (T[]): Array of objects to sort
  • key (keyof T): Property name to sort by
  • order (SortOrder, optional): 'asc' or 'desc' (default: 'asc')

Returns:

  • T[]: New sorted array

Example:

import { sortByKey } from '@/utils/helpers/lodash';

const employees = [
{ name: 'John', age: 30, salary: 75000 },
{ name: 'Jane', age: 25, salary: 80000 },
{ name: 'Bob', age: 35, salary: 70000 },
{ name: 'Alice', age: 28, salary: 85000 },
];

// Sort by age (ascending)
const sortedByAge = sortByKey(employees, 'age');
console.log(sortedByAge);
// Returns: [
// { name: 'Jane', age: 25, salary: 80000 },
// { name: 'Alice', age: 28, salary: 85000 },
// { name: 'John', age: 30, salary: 75000 },
// { name: 'Bob', age: 35, salary: 70000 }
// ]

// Sort by salary (descending)
const sortedBySalary = sortByKey(employees, 'salary', 'desc');
console.log(sortedBySalary);
// Returns: [
// { name: 'Alice', age: 28, salary: 85000 },
// { name: 'Jane', age: 25, salary: 80000 },
// { name: 'John', age: 30, salary: 75000 },
// { name: 'Bob', age: 35, salary: 70000 }
// ]

// Sort by name (alphabetical)
const sortedByName = sortByKey(employees, 'name');
console.log(sortedByName);
// Returns: [
// { name: 'Alice', age: 28, salary: 85000 },
// { name: 'Bob', age: 35, salary: 70000 },
// { name: 'Jane', age: 25, salary: 80000 },
// { name: 'John', age: 30, salary: 75000 }
// ]

Use Cases:

  • Table sorting functionality
  • Data presentation ordering
  • Search result ranking
  • Report generation

Advanced Usage Examples

Data Sanitization Pipeline

import { omit, uniqBy, sortByKey } from '@/utils/helpers/lodash';

const sanitizeAndProcessUsers = rawUsers => {
// Step 1: Remove sensitive fields
const publicUsers = rawUsers.map(user =>
omit(user, ['password', 'internalNotes', 'ssn']),
);

// Step 2: Remove duplicates by email
const uniqueUsers = uniqBy(publicUsers, 'email');

// Step 3: Sort by creation date (newest first)
const sortedUsers = sortByKey(uniqueUsers, 'createdAt', 'desc');

return sortedUsers;
};

// Usage
const rawData = [
{
id: 1,
email: 'john@example.com',
name: 'John',
password: 'secret',
createdAt: '2024-01-15',
},
{
id: 2,
email: 'jane@example.com',
name: 'Jane',
password: 'secret2',
createdAt: '2024-01-10',
},
{
id: 3,
email: 'john@example.com',
name: 'John Duplicate',
password: 'secret3',
createdAt: '2024-01-20',
},
];

const cleanUsers = sanitizeAndProcessUsers(rawData);
// Returns clean, unique, sorted user data

Table Sorting Hook

import { sortByKey } from '@/utils/helpers/lodash';

const useTableSort = (initialData = []) => {
const [data, setData] = useState(initialData);
const [sortConfig, setSortConfig] = useState({
key: null,
direction: 'asc',
});

const handleSort = key => {
let direction = 'asc';

if (sortConfig.key === key && sortConfig.direction === 'asc') {
direction = 'desc';
}

const sortedData = sortByKey([...data], key, direction);

setData(sortedData);
setSortConfig({ key, direction });
};

const resetSort = () => {
setData(initialData);
setSortConfig({ key: null, direction: 'asc' });
};

return {
data,
sortConfig,
handleSort,
resetSort,
setData,
};
};

// Usage in component
const EmployeeTable = ({ employees }) => {
const { data, sortConfig, handleSort } = useTableSort(employees);

return (
<table>
<thead>
<tr>
<th onClick={() => handleSort('name')}>
Name{' '}
{sortConfig.key === 'name' &&
(sortConfig.direction === 'asc' ? '↑' : '↓')}
</th>
<th onClick={() => handleSort('department')}>
Department{' '}
{sortConfig.key === 'department' &&
(sortConfig.direction === 'asc' ? '↑' : '↓')}
</th>
<th onClick={() => handleSort('salary')}>
Salary{' '}
{sortConfig.key === 'salary' &&
(sortConfig.direction === 'asc' ? '↑' : '↓')}
</th>
</tr>
</thead>
<tbody>
{data.map(employee => (
<tr key={employee.id}>
<td>{employee.name}</td>
<td>{employee.department}</td>
<td>{employee.salary}</td>
</tr>
))}
</tbody>
</table>
);
};

API Response Processing

import { omit, uniqBy, getFirstItemFromArray } from '@/utils/helpers/lodash';

const processApiResponse = response => {
// Get data from potentially nested response
const data = getFirstItemFromArray(response.data || response.results || []);

if (Array.isArray(data)) {
// Process array of items
return data
.map(item => omit(item, ['_internal', 'debug']))
.filter(item => item.active !== false);
}

// Process single item
return omit(data, ['_internal', 'debug']);
};

// Usage with error handling
const fetchAndProcessData = async endpoint => {
try {
const response = await fetch(endpoint);
const rawData = await response.json();

return processApiResponse(rawData);
} catch (error) {
console.error('API processing failed:', error);
return [];
}
};

Form Data Processing

import { omit, sortByKey } from '@/utils/helpers/lodash';

const useFormProcessor = () => {
const cleanFormData = formData => {
// Remove empty fields and internal form state
return omit(formData, ['__meta', '__errors', '__touched']);
};

const processSelectOptions = options => {
// Remove duplicates and sort options
const uniqueOptions = uniqBy(options, 'value');
return sortByKey(uniqueOptions, 'label');
};

const prepareForSubmission = formData => {
const cleaned = cleanFormData(formData);

// Convert empty strings to null
return Object.fromEntries(
Object.entries(cleaned).map(([key, value]) => [
key,
value === '' ? null : value,
]),
);
};

return {
cleanFormData,
processSelectOptions,
prepareForSubmission,
};
};

Data Grouping and Organization

import { uniqBy, sortByKey } from '@/utils/helpers/lodash';

const organizeEmployeeData = employees => {
// Get unique departments
const departments = uniqBy(employees, 'department').map(emp => ({
name: emp.department,
id: emp.departmentId,
}));

// Sort departments alphabetically
const sortedDepartments = sortByKey(departments, 'name');

// Group employees by department
return sortedDepartments.map(dept => ({
...dept,
employees: sortByKey(
employees.filter(emp => emp.department === dept.name),
'lastName',
),
}));
};

// Usage in component
const DepartmentView = ({ employees }) => {
const departments = useMemo(
() => organizeEmployeeData(employees),
[employees],
);

return (
<div>
{departments.map(dept => (
<div key={dept.id}>
<h3>{dept.name}</h3>
<ul>
{dept.employees.map(emp => (
<li key={emp.id}>
{emp.firstName} {emp.lastName}
</li>
))}
</ul>
</div>
))}
</div>
);
};

Performance Considerations

  • Memory Efficiency: All functions create new objects/arrays instead of mutating
  • Type Safety: Functions preserve TypeScript types where possible
  • Bundle Size: Lightweight implementations reduce overall bundle size
  • Browser Support: Compatible with modern browser environments

Comparison with Full Lodash

FunctionLodash SizeOur ImplementationFeatures
omit~4KB~100BBasic property omission
uniqBy~3KB~200BProperty/function-based uniqueness
sortBy~2KB~150BBasic sorting with direction
head~1KB~50BSafe first element access

Best Practices

  1. Type Safety: Use TypeScript interfaces for better type checking
  2. Null Safety: Functions handle null/undefined inputs gracefully
  3. Immutability: All functions return new objects/arrays
  4. Performance: Consider memoization for expensive operations
  5. Testing: Test edge cases like empty arrays and null values


TypeScript Definitions

type SortOrder = 'asc' | 'desc';

export function omit(o: any, paths?: string[]): any;
export function getFirstItemFromArray&lt;T&gt;(items: T | T[]): T | undefined;
export function uniqBy&lt;T&gt;(arr: T[], iteratee: string | ((item: T) => any)): T[];
export function sortByKey&lt;T&gt;(
collection: T[],
key: keyof T,
order?: SortOrder,
): T[];

Browser Compatibility

  • ES6+: Uses modern JavaScript features
  • Array Methods: Relies on native filter, map, findIndex
  • Object Methods: Uses Object.entries, Object.fromEntries
  • Spread Operator: Uses spread syntax for array/object cloning

Bundle Impact

Using these helpers instead of full Lodash can reduce bundle size by:

  • Before: ~67KB (gzipped: ~25KB) for common Lodash functions
  • After: ~1KB (gzipped: ~400B) for these implementations
  • Savings: ~99% reduction in bundle size for these functions