Skip to content

Comparison Strategies

Optimization patterns for choosing the right comparison strategy in store subscriptions to balance performance and accuracy.

Reference Comparison (Fastest)

Use reference comparison for primitive values or when exact object reference matters:

tsx
// For primitive values or when exact reference matters
const primitiveValue = useStoreValue(store, undefined, {
  comparison: 'reference' // Default, fastest
});

Best for:

  • Primitive values (string, number, boolean)
  • When object references are carefully managed
  • Maximum performance requirements

Shallow Comparison (Balanced)

Use shallow comparison for objects with shallow changes:

tsx
// For objects with shallow changes
const shallowData = useStoreValue(settingsStore, undefined, {
  comparison: 'shallow' // Good balance of performance and accuracy
});

Best for:

  • Objects with first-level property changes
  • Configuration objects
  • Most common use cases where deep nesting isn't a concern

Deep Comparison (Most Accurate)

Use deep comparison only when necessary for complex nested objects:

tsx
// Only when necessary for complex nested objects
const deepData = useStoreValue(complexStore, undefined, {
  comparison: 'deep' // Most thorough, use sparingly
});

Best for:

  • Complex nested object structures
  • When accuracy is more important than performance
  • Data with unpredictable nesting levels

Custom Comparison

Create custom comparators for specific business logic:

tsx
const customComparison = useStoreValue(userStore, user => user.profile, {
  customComparator: (prev, next) => {
    // Only update if specific fields changed
    return prev.name === next.name && prev.avatar === next.avatar;
  }
});

Best for:

  • Specific business logic requirements
  • When only certain fields matter for updates
  • Performance optimization for known data structures

Circular Reference Safe Comparison

Handle circular references safely in custom comparators:

tsx
// ✅ CORRECT: Safe handling of circular references
const circularSafeComparison = useStoreValue(complexStore, undefined, {
  customComparator: (prev, next) => {
    const seenObjects = new WeakSet();
    
    try {
      // Use JSON.stringify for simple circular reference detection
      const prevStr = JSON.stringify(prev, (key, value) => {
        if (typeof value === 'object' && value !== null) {
          if (seenObjects.has(value)) return '[Circular]';
          seenObjects.add(value);
        }
        return value;
      });
      
      const nextStr = JSON.stringify(next, (key, value) => {
        if (typeof value === 'object' && value !== null) {
          if (seenObjects.has(value)) return '[Circular]';
          seenObjects.add(value);
        }
        return value;
      });
      
      return prevStr === nextStr;
    } catch {
      // Fallback to reference comparison for complex circular structures
      return Object.is(prev, next);
    }
  }
});

Performance Characteristics

Comparison Strategy Performance

StrategySpeedAccuracyMemoryUse Case
Reference⚡⚡⚡✅ (primitives)⚡⚡⚡Primitives, managed references
Shallow⚡⚡✅✅⚡⚡Objects with shallow changes
Deep✅✅✅Complex nested structures
Custom⚡⚡✅✅⚡⚡Specific business logic

Choosing the Right Strategy

  1. Start with Reference: Default for most primitive values
  2. Upgrade to Shallow: When objects have shallow property changes
  3. Consider Custom: For specific business logic or known data patterns
  4. Use Deep Sparingly: Only when nested changes must trigger updates

Best Practices

✅ Do

  • Start with reference comparison and upgrade as needed
  • Use shallow comparison for most object scenarios
  • Profile comparison performance in development
  • Consider custom comparators for specific business logic

❌ Avoid

  • Using deep comparison unnecessarily
  • Creating expensive custom comparators
  • Ignoring circular reference handling in custom comparators
  • Over-optimizing comparison strategies

Released under the Apache-2.0 License.