Performance Tips
Performance optimization tips for FoundryKit utilities
Performance Tips
Learn how to optimize performance when using FoundryKit utilities in your applications.
Bundle Size Optimization
Tree Shaking
Import only the utilities you need to minimize bundle size.
// ❌ Avoid importing the entire package
import * as utils from '@foundrykit/utils'
// ✅ Use named imports for tree shaking
import { cn, debounce, formatDate } from '@foundrykit/utils'
// ✅ Or import from specific modules
import { cn } from '@foundrykit/utils/cn'
import { debounce } from '@foundrykit/utils/function'
import { formatDate } from '@foundrykit/utils/date'
Dynamic Imports
Use dynamic imports for utilities that aren't needed immediately.
import React, { useState, lazy, Suspense } from 'react'
// Lazy load heavy utilities
const LazyDataProcessor = lazy(() => import('./DataProcessor'))
// Dynamic import for occasional use
async function processLargeDataset(data: any[]) {
const { chunk, sortBy, groupBy } = await import('@foundrykit/utils')
// Process data only when needed
const chunks = chunk(data, 1000)
return chunks.map(chunk => sortBy(chunk, 'timestamp'))
}
export function DataDashboard() {
const [showProcessor, setShowProcessor] = useState(false)
return (
<div>
<button onClick={() => setShowProcessor(true)}>
Load Data Processor
</button>
{showProcessor && (
<Suspense fallback={<div>Loading processor...</div>}>
<LazyDataProcessor />
</Suspense>
)}
</div>
)
}
Bundle Analysis
Monitor your bundle size to track utility usage.
# Analyze bundle size
npm run build -- --analyze
# Check specific utility impact
npm run bundle-analyzer
Memory Management
Memoization Best Practices
Use memoization strategically to avoid memory leaks.
import { memoize, debounce } from '@foundrykit/utils'
// ✅ Good: Memoize pure functions with stable inputs
const calculateExpensiveValue = memoize((data: number[]) => {
return data.reduce((sum, val) => sum + Math.pow(val, 2), 0)
})
// ✅ Good: Clear cache when data changes significantly
const processUserData = memoize((users: User[]) => {
return users.map(user => ({
...user,
displayName: `${user.firstName} ${user.lastName}`
}))
})
// Clear cache when users change
useEffect(() => {
processUserData.cache.clear()
}, [usersVersion])
// ❌ Avoid: Memoizing functions with constantly changing inputs
const badMemoization = memoize((timestamp: number, data: any) => {
// This will create a new cache entry for every timestamp
return processData(data)
})
// ✅ Better: Memoize the stable part only
const goodMemoization = memoize((data: any) => {
return processData(data)
})
Debounce and Throttle Cleanup
Properly clean up debounced and throttled functions.
import React, { useEffect, useMemo } from 'react'
import { debounce, throttle } from '@foundrykit/utils'
export function SearchComponent() {
const [query, setQuery] = useState('')
// Create debounced function with cleanup
const debouncedSearch = useMemo(
() => debounce((searchQuery: string) => {
performSearch(searchQuery)
}, 300),
[]
)
const throttledScroll = useMemo(
() => throttle(() => {
updateScrollPosition()
}, 100),
[]
)
useEffect(() => {
// Add scroll listener
window.addEventListener('scroll', throttledScroll)
// Cleanup on unmount
return () => {
window.removeEventListener('scroll', throttledScroll)
debouncedSearch.cancel() // Cancel pending debounced calls
throttledScroll.cancel() // Cancel pending throttled calls
}
}, [debouncedSearch, throttledScroll])
return (
<input
onChange={(e) => {
setQuery(e.target.value)
debouncedSearch(e.target.value)
}}
value={query}
/>
)
}
Computation Optimization
Efficient Data Processing
Optimize data processing operations for large datasets.
import { chunk, groupBy, sortBy, unique } from '@foundrykit/utils'
// ✅ Process large datasets in chunks
function processLargeDataset(data: any[], chunkSize: number = 1000) {
const chunks = chunk(data, chunkSize)
return chunks.map(chunk => {
// Process each chunk separately to avoid blocking the main thread
return new Promise(resolve => {
setTimeout(() => {
const processed = chunk.map(item => processItem(item))
resolve(processed)
}, 0)
})
})
}
// ✅ Use Web Workers for CPU-intensive operations
function processDataInWorker(data: any[]) {
return new Promise((resolve, reject) => {
const worker = new Worker('/data-processor-worker.js')
worker.postMessage({ data })
worker.onmessage = (e) => {
resolve(e.data.result)
worker.terminate()
}
worker.onerror = (error) => {
reject(error)
worker.terminate()
}
})
}
// ✅ Optimize sorting and grouping operations
function optimizeDataOperations(data: any[]) {
// Sort once, use multiple times
const sortedData = sortBy(data, 'timestamp')
// Group the already sorted data
const groupedData = groupBy(sortedData, 'category')
// Remove duplicates efficiently
const uniqueData = unique(sortedData, 'id')
return { sortedData, groupedData, uniqueData }
}
// ❌ Avoid: Multiple expensive operations on the same data
function inefficientOperations(data: any[]) {
const sorted1 = sortBy(data, 'timestamp') // Sort #1
const grouped = groupBy(data, 'category') // Iterates again
const sorted2 = sortBy(data, 'name') // Sort #2
const unique = unique(data, 'id') // Iterates again
return { sorted1, grouped, sorted2, unique }
}
Lazy Evaluation
Implement lazy evaluation for expensive operations.
import { memoize } from '@foundrykit/utils'
class LazyDataProcessor {
private data: any[]
private _sorted?: any[]
private _grouped?: Record<string, any[]>
private _filtered?: any[]
constructor(data: any[]) {
this.data = data
}
// Lazy getter for sorted data
get sorted() {
if (!this._sorted) {
this._sorted = sortBy(this.data, 'timestamp')
}
return this._sorted
}
// Lazy getter for grouped data
get grouped() {
if (!this._grouped) {
this._grouped = groupBy(this.data, 'category')
}
return this._grouped
}
// Lazy method with memoization
getFiltered = memoize((predicate: (item: any) => boolean) => {
return this.data.filter(predicate)
})
// Reset cached values when data changes
updateData(newData: any[]) {
this.data = newData
this._sorted = undefined
this._grouped = undefined
this.getFiltered.cache.clear()
}
}
// Usage
const processor = new LazyDataProcessor(largeDataset)
// Only computed when accessed
const sortedData = processor.sorted
const groupedData = processor.grouped
const filteredData = processor.getFiltered(item => item.active)
React Performance Integration
Optimized Hook Usage
Integrate utilities with React hooks efficiently.
import React, { useMemo, useCallback, useState } from 'react'
import { debounce, sortBy, groupBy, unique } from '@foundrykit/utils'
interface UseDataProcessorOptions {
sortBy?: string
groupBy?: string
filterFn?: (item: any) => boolean
debounceMs?: number
}
function useDataProcessor(data: any[], options: UseDataProcessorOptions = {}) {
const {
sortBy: sortField,
groupBy: groupField,
filterFn,
debounceMs = 300
} = options
const [query, setQuery] = useState('')
const [debouncedQuery, setDebouncedQuery] = useState('')
// Debounce query updates
const debouncedSetQuery = useMemo(
() => debounce((value: string) => {
setDebouncedQuery(value)
}, debounceMs),
[debounceMs]
)
// Update query and trigger debounced update
const updateQuery = useCallback((value: string) => {
setQuery(value)
debouncedSetQuery(value)
}, [debouncedSetQuery])
// Process data with memoization
const processedData = useMemo(() => {
let result = data
// Apply text filter
if (debouncedQuery) {
const queryLower = debouncedQuery.toLowerCase()
result = result.filter(item =>
Object.values(item).some(value =>
String(value).toLowerCase().includes(queryLower)
)
)
}
// Apply custom filter
if (filterFn) {
result = result.filter(filterFn)
}
// Sort if specified
if (sortField) {
result = sortBy(result, sortField)
}
return result
}, [data, debouncedQuery, filterFn, sortField])
// Group data if specified
const groupedData = useMemo(() => {
if (!groupField) return null
return groupBy(processedData, groupField)
}, [processedData, groupField])
// Get unique values for a field
const getUniqueValues = useCallback((field: string) => {
return unique(data.map(item => item[field]))
}, [data])
// Cleanup on unmount
useEffect(() => {
return () => {
debouncedSetQuery.cancel()
}
}, [debouncedSetQuery])
return {
query,
updateQuery,
processedData,
groupedData,
getUniqueValues,
isProcessing: query !== debouncedQuery
}
}
// Usage in component
export function DataTable({ data }: { data: any[] }) {
const {
query,
updateQuery,
processedData,
groupedData,
getUniqueValues,
isProcessing
} = useDataProcessor(data, {
sortBy: 'name',
groupBy: 'category',
debounceMs: 250
})
const categories = useMemo(() => getUniqueValues('category'), [getUniqueValues])
return (
<div>
<input
value={query}
onChange={(e) => updateQuery(e.target.value)}
placeholder="Search..."
className="mb-4 p-2 border rounded"
/>
{isProcessing && (
<div className="text-sm text-gray-500">Processing...</div>
)}
<div className="grid gap-4">
{groupedData ? (
Object.entries(groupedData).map(([category, items]) => (
<div key={category}>
<h3 className="font-bold">{category}</h3>
<div className="grid gap-2">
{items.map((item, index) => (
<div key={index} className="p-2 border rounded">
{item.name}
</div>
))}
</div>
</div>
))
) : (
processedData.map((item, index) => (
<div key={index} className="p-2 border rounded">
{item.name}
</div>
))
)}
</div>
</div>
)
}
Virtual Scrolling with Utilities
Implement virtual scrolling for large lists using utilities.
import React, { useMemo, useState, useCallback } from 'react'
import { chunk, sortBy } from '@foundrykit/utils'
interface VirtualizedListProps {
items: any[]
itemHeight: number
containerHeight: number
renderItem: (item: any, index: number) => React.ReactNode
sortField?: string
}
export function VirtualizedList({
items,
itemHeight,
containerHeight,
renderItem,
sortField
}: VirtualizedListProps) {
const [scrollTop, setScrollTop] = useState(0)
// Sort items if needed
const sortedItems = useMemo(() => {
return sortField ? sortBy(items, sortField) : items
}, [items, sortField])
// Calculate visible range
const visibleRange = useMemo(() => {
const startIndex = Math.floor(scrollTop / itemHeight)
const endIndex = Math.min(
startIndex + Math.ceil(containerHeight / itemHeight) + 1,
sortedItems.length
)
return { startIndex, endIndex }
}, [scrollTop, itemHeight, containerHeight, sortedItems.length])
// Get visible items
const visibleItems = useMemo(() => {
return sortedItems.slice(visibleRange.startIndex, visibleRange.endIndex)
}, [sortedItems, visibleRange])
// Handle scroll
const handleScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
setScrollTop(e.currentTarget.scrollTop)
}, [])
const totalHeight = sortedItems.length * itemHeight
const offsetY = visibleRange.startIndex * itemHeight
return (
<div
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={handleScroll}
>
<div style={{ height: totalHeight, position: 'relative' }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map((item, index) =>
renderItem(item, visibleRange.startIndex + index)
)}
</div>
</div>
</div>
)
}
Performance Monitoring
Utility Performance Tracking
Monitor the performance of utility functions.
import { memoize, debounce } from '@foundrykit/utils'
// Performance monitoring wrapper
function withPerformanceTracking<T extends (...args: any[]) => any>(
fn: T,
name: string
): T {
return ((...args: any[]) => {
const start = performance.now()
const result = fn(...args)
const end = performance.now()
if (end - start > 10) { // Log slow operations (>10ms)
console.warn(`Slow utility function ${name}: ${(end - start).toFixed(2)}ms`)
}
return result
}) as T
}
// Wrap expensive utilities
const trackedSortBy = withPerformanceTracking(sortBy, 'sortBy')
const trackedGroupBy = withPerformanceTracking(groupBy, 'groupBy')
// Performance-aware memoization
const performanceMemoize = <T extends (...args: any[]) => any>(
fn: T,
name: string
) => {
const memoized = memoize(fn)
return ((...args: any[]) => {
const start = performance.now()
const result = memoized(...args)
const end = performance.now()
const cacheHit = end - start < 1 // Assume cache hit if very fast
if (!cacheHit && end - start > 50) {
console.warn(`Slow memoized function ${name}: ${(end - start).toFixed(2)}ms`)
}
return result
}) as T
}
Bundle Size Monitoring
Track utility bundle impact in your build process.
// webpack.config.js or similar
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html'
})
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
utils: {
test: /[\\/]node_modules[\\/]@foundrykit[\\/]utils/,
name: 'foundrykit-utils',
chunks: 'all'
}
}
}
}
}
Performance Best Practices
Do's and Don'ts
// ✅ DO: Use utilities efficiently
const processData = memoize((data: any[]) => {
// Combine multiple operations efficiently
const sorted = sortBy(data, 'timestamp')
const grouped = groupBy(sorted, 'category')
return { sorted, grouped }
})
// ❌ DON'T: Create new utility instances in render
function BadComponent({ data }) {
return (
<div>
{data.map(item => {
// Creates new debounced function on every render
const debouncedFn = debounce(() => {}, 300)
return <div key={item.id}>{item.name}</div>
})}
</div>
)
}
// ✅ DO: Create utility instances outside render or with useMemo
function GoodComponent({ data }) {
const debouncedFn = useMemo(() => debounce(() => {}, 300), [])
return (
<div>
{data.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
)
}
// ✅ DO: Use appropriate utility for the task
const largeArray = Array.from({ length: 10000 }, (_, i) => ({ id: i, value: Math.random() }))
// Efficient for large arrays
const chunked = chunk(largeArray, 100)
const sorted = sortBy(largeArray, 'value')
// ❌ DON'T: Use utilities unnecessarily for small datasets
const smallArray = [1, 2, 3, 4, 5]
// Overkill for small arrays
const unnecessaryChunks = chunk(smallArray, 2) // Just use array methods
// ✅ Better for small arrays
const simpleChunks = [smallArray.slice(0, 2), smallArray.slice(2, 4), smallArray.slice(4)]
Performance Checklist
- Import only needed utilities (tree shaking)
- Use memoization for expensive computations
- Clear memoization caches when data changes
- Cancel debounced/throttled functions on cleanup
- Process large datasets in chunks
- Use lazy evaluation for optional computations
- Monitor bundle size impact
- Profile performance in development
- Use appropriate utilities for data size
- Avoid creating utility instances in render loops
Next Steps
- Review best practices for optimal usage
- Explore the utility functions reference for detailed API documentation
- Learn about common patterns for effective usage