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