Computed Value
Problem
Expensive calculations run on every render even when their inputs haven’t changed, causing performance degradation. Filtering large lists, sorting data, or formatting values repeats unnecessarily. Components freeze during typing because each keystroke triggers heavy computations. The CPU spins doing the same work over and over with identical inputs.
Solution
Derive new values from existing state rather than storing redundant data. This keeps your state minimal and ensures calculated values stay synchronized with their sources automatically.
Example
This example demonstrates a computed value class that caches expensive calculations and only recomputes when dependencies change.
// Framework-agnostic computed value with dependency tracking
class ComputedValue {
constructor(fn, getDependencies) {
this.fn = fn; // The computation to cache
this.getDependencies = getDependencies; // Function that returns dependency array
this.cache = null; // Cached result
this.lastDeps = null; // Previous dependencies for comparison
}
get() {
// Get current dependency values
const currentDeps = this.getDependencies();
// Check if any dependency has changed since last computation
const depsChanged = !this.lastDeps ||
currentDeps.some((dep, i) => dep !== this.lastDeps[i]);
// Only recompute if dependencies changed
if (depsChanged) {
this.cache = this.fn(); // Run the expensive computation
this.lastDeps = currentDeps; // Store current deps for next comparison
}
// Return cached result
return this.cache;
}
}
// Usage: Create a computed value that filters items by category
const filteredItems = new ComputedValue(
// Computation function - only runs when dependencies change
() => items.filter(item => item.category === selectedCategory),
// Dependencies - when these change, recompute
() => [items, selectedCategory]
);
// Get the computed value - uses cache if dependencies haven't changed
const result = filteredItems.get();
Benefits
- Prevents expensive calculations from running on every render unnecessarily.
- Improves performance by caching results until dependencies change.
- Keeps state minimal by avoiding redundant derived data storage.
- Ensures calculated values stay synchronized with their source automatically.
- Reduces CPU usage and improves responsiveness during user interactions.
Tradeoffs
- Adds complexity with dependency tracking that can be error-prone.
- Premature memoization can make code harder to understand for little gain.
- Memory overhead for cached values, though usually negligible.
- Can hide performance issues if dependencies are incorrectly specified.
- Debugging can be harder when values are computed rather than stored directly.