Back to QuantaJS Blog

Sunday, November 16, 2025

Performance Optimization with QuantaJS: A Complete Guide

Cover image for Performance Optimization with QuantaJS: A Complete Guide

Performance Optimization with QuantaJS: A Complete Guide

Performance is crucial for creating smooth, responsive user experiences. QuantaJS is designed with performance in mind, but understanding how to use it effectively can make a significant difference in your application's speed and responsiveness. In this guide, we'll explore practical strategies to optimize your QuantaJS applications.

Understanding QuantaJS Performance Characteristics

QuantaJS uses a reactive system that tracks dependencies and only updates what's necessary. However, like any state management solution, improper usage can lead to performance issues. Let's explore how to avoid common pitfalls and maximize performance.

1. Use Selectors for Fine-Grained Updates

One of the most important performance optimizations is using selectors to subscribe only to the data you need.

❌ Bad: Subscribing to Entire Store

function UserProfile() {
  const store = useStore('user');
  
  // Component re-renders on ANY store change
  return <div>{store.name}</div>;
}

✅ Good: Using Selectors

function UserProfile() {
  // Only re-renders when name changes
  const name = useStore('user', store => store.name);
  
  return <div>{name}</div>;
}

Real-World Example: Todo List

// ❌ Bad: Re-renders entire list when any todo changes
function TodoList() {
  const store = useStore('todos');
  
  return (
    <ul>
      {store.todos.map(todo => (
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </ul>
  );
}

// ✅ Good: Only re-renders when todos array changes
function TodoList() {
  const todos = useStore('todos', store => store.todos);
  
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </ul>
  );
}

// ✅ Better: Individual items only re-render when their data changes
function TodoItem({ todoId }) {
  const todo = useStore('todos', store => 
    store.todos.find(t => t.id === todoId)
  );
  
  return <li>{todo?.text}</li>;
}

2. Optimize Computed Values (Getters)

Computed values are cached and only recalculate when dependencies change. Use them effectively:

✅ Good: Computed Values for Derived State

const todoStore = createStore('todos', {
  state: () => ({
    todos: [],
    filter: 'all'
  }),
  getters: {
    // Only recalculates when todos or filter changes
    filteredTodos: (state) => {
      switch (state.filter) {
        case 'active':
          return state.todos.filter(t => !t.done);
        case 'completed':
          return state.todos.filter(t => t.done);
        default:
          return state.todos;
      }
    },
    // Cached until dependencies change
    completedCount: (state) => 
      state.todos.filter(t => t.done).length,
    activeCount: (state) => 
      state.todos.filter(t => !t.done).length
  }
});

❌ Avoid: Expensive Operations in Components

// ❌ Bad: Recalculates on every render
function TodoStats() {
  const todos = useStore('todos', store => store.todos);
  const completed = todos.filter(t => t.done).length; // Expensive!
  
  return <div>Completed: {completed}</div>;
}

// ✅ Good: Use computed getter
function TodoStats() {
  const completedCount = useStore('todos', store => store.completedCount);
  
  return <div>Completed: {completedCount}</div>;
}

3. Optimize Watch Usage

The watch function is powerful but can impact performance if not used carefully.

✅ Good: Specific Watchers

// Only watches specific value
watch(() => userStore.userId, (userId) => {
  if (userId) {
    fetchUserData(userId);
  }
});

❌ Avoid: Watching Large Objects

// ❌ Bad: Watches entire large object
watch(() => appStore, (store) => {
  // This triggers on ANY change to the store
  console.log('Store changed');
});

// ✅ Good: Watch specific values
watch(() => appStore.currentView, (view) => {
  console.log('View changed:', view);
});

Deep Watching Considerations

// ⚠️ Use deep watching sparingly - it uses polling
watch(() => settingsStore.preferences, (prefs) => {
  // Only use deep: true when necessary
}, { deep: true });

// ✅ Better: Watch specific nested properties
watch(() => settingsStore.preferences.theme, (theme) => {
  applyTheme(theme);
});

4. Store Organization Strategies

How you organize your stores can significantly impact performance.

✅ Good: Domain-Based Stores

// Separate stores by domain
const userStore = createStore('user', { /* ... */ });
const cartStore = createStore('cart', { /* ... */ });
const settingsStore = createStore('settings', { /* ... */ });

// Components only subscribe to what they need
function CartSummary() {
  const total = useStore('cart', store => store.total);
  return <div>Total: ${total}</div>;
}

❌ Avoid: Monolithic Stores

// ❌ Bad: One giant store
const appStore = createStore('app', {
  state: () => ({
    user: { /* ... */ },
    cart: { /* ... */ },
    settings: { /* ... */ },
    todos: { /* ... */ },
    // ... everything in one store
  })
});

// Any change triggers re-renders in all components

5. Minimize State Updates

Reduce the number of state updates by batching changes when possible.

✅ Good: Batch Updates

const todoStore = createStore('todos', {
  state: () => ({ todos: [] }),
  actions: {
    // Single update instead of multiple
    addMultipleTodos(newTodos) {
      this.todos = [...this.todos, ...newTodos];
    },
    
    // Batch filter updates
    updateFilters(filters) {
      Object.assign(this.filters, filters);
    }
  }
});

❌ Avoid: Multiple Sequential Updates

// ❌ Bad: Multiple updates trigger multiple re-renders
function addTodos() {
  todoStore.addTodo(todo1); // Re-render
  todoStore.addTodo(todo2); // Re-render
  todoStore.addTodo(todo3); // Re-render
}

// ✅ Good: Single batch update
function addTodos() {
  todoStore.addMultipleTodos([todo1, todo2, todo3]); // One re-render
}

6. Use Component-Scoped Stores When Appropriate

For local state that doesn't need to be shared, use component-scoped stores.

✅ Good: Component-Scoped State

function TodoForm() {
  // Local state - doesn't affect other components
  const formStore = useCreateStore('todo-form', () => ({
    text: '',
    priority: 'medium'
  }));
  
  return (
    <form>
      <input 
        value={formStore.text}
        onChange={(e) => formStore.text = e.target.value}
      />
    </form>
  );
}

When to Use Component-Scoped vs Global Stores

  • Component-Scoped: Form state, UI state (modals, dropdowns), temporary data
  • Global Stores: User data, cart, settings, shared application state

7. Optimize Persistence

Persistence can impact performance if not configured properly.

✅ Good: Selective Persistence

const appStore = createStore('app', {
  state: () => ({
    user: null,
    settings: {},
    temporaryData: [],
    cache: new Map()
  }),
  persist: {
    adapter: new LocalStorageAdapter('app-store'),
    // Only persist what's necessary
    include: ['user', 'settings'],
    exclude: ['temporaryData', 'cache'],
    // Debounce saves
    debounceMs: 500
  }
});

⚠️ Avoid: Persisting Everything

// ❌ Bad: Persisting large, frequently-changing data
persist: {
  adapter: new LocalStorageAdapter('store'),
  // Persists everything, including large arrays
}

8. Performance Monitoring

Use QuantaJS logger to monitor performance in development.

import { logger, LogLevel } from '@quantajs/core';

// Configure logger for development
if (process.env.NODE_ENV === 'development') {
  logger.configure({
    level: LogLevel.DEBUG,
    prefix: 'Performance'
  });
}

// Log store operations
const todoStore = createStore('todos', {
  state: () => ({ todos: [] }),
  actions: {
    addTodo(text) {
      const start = performance.now();
      this.todos.push({ id: Date.now(), text });
      const end = performance.now();
      logger.debug(`addTodo took ${end - start}ms`);
    }
  }
});

9. React-Specific Optimizations

Use useQuantaStore for Direct Store References

// ✅ Good: Direct store reference with selector
function CartIcon() {
  const itemCount = useQuantaStore(cartStore, store => store.itemCount);
  return <span>{itemCount}</span>;
}

// This avoids context lookups and is more performant

Memoize Expensive Selectors

import { useMemo } from 'react';

function ExpensiveComponent() {
  const todos = useStore('todos', store => store.todos);
  
  // Memoize expensive computations
  const expensiveValue = useMemo(() => {
    return todos
      .filter(/* complex filter */)
      .map(/* complex map */)
      .reduce(/* complex reduce */);
  }, [todos]);
  
  return <div>{expensiveValue}</div>;
}

10. Best Practices Summary

✅ Do's

  • Use selectors to subscribe only to needed data
  • Leverage computed values (getters) for derived state
  • Organize stores by domain
  • Use component-scoped stores for local state
  • Batch related state updates
  • Configure persistence selectively
  • Use useQuantaStore for direct store access when appropriate

❌ Don'ts

  • Don't subscribe to entire stores when you only need specific values
  • Don't perform expensive calculations in components
  • Don't watch large objects unnecessarily
  • Don't create monolithic stores
  • Don't persist everything
  • Don't make multiple sequential updates when you can batch them

Real-World Example: Optimized E-Commerce Store

// Optimized store structure
const productStore = createStore('products', {
  state: () => ({
    products: [],
    filters: { category: 'all', priceRange: [0, 1000] },
    sortBy: 'name'
  }),
  getters: {
    // Computed: Only recalculates when dependencies change
    filteredProducts: (state) => {
      return state.products
        .filter(p => {
          const categoryMatch = state.filters.category === 'all' || 
                               p.category === state.filters.category;
          const priceMatch = p.price >= state.filters.priceRange[0] && 
                            p.price <= state.filters.priceRange[1];
          return categoryMatch && priceMatch;
        })
        .sort((a, b) => {
          // Sort logic based on sortBy
        });
    },
    productCount: (state) => state.products.length
  },
  actions: {
    // Batch filter updates
    updateFilters(newFilters) {
      Object.assign(this.filters, newFilters);
    }
  }
});

// Optimized components
function ProductList() {
  // Only subscribes to filtered products
  const products = useStore('products', store => store.filteredProducts);
  
  return (
    <div>
      {products.map(product => (
        <ProductCard key={product.id} productId={product.id} />
      ))}
    </div>
  );
}

function ProductCard({ productId }) {
  // Individual cards only re-render when their product changes
  const product = useStore('products', store => 
    store.products.find(p => p.id === productId)
  );
  
  return <div>{product?.name}</div>;
}

function ProductFilters() {
  // Only subscribes to filter state
  const filters = useStore('products', store => store.filters);
  const updateFilters = useStore('products', store => store.updateFilters);
  
  return (
    <div>
      {/* Filter UI */}
    </div>
  );
}

Performance Checklist

Before deploying your QuantaJS application, check:

  • Are you using selectors instead of subscribing to entire stores?
  • Are expensive calculations in computed getters?
  • Are stores organized by domain?
  • Is persistence configured selectively?
  • Are state updates batched when possible?
  • Are you using component-scoped stores for local state?
  • Are watch functions watching specific values?
  • Is deep watching used only when necessary?

Conclusion

Performance optimization in QuantaJS is about understanding how reactivity works and using the right patterns for your use case. By following these guidelines, you can build fast, responsive applications that scale well.

Remember:

  • Measure first: Use browser DevTools to identify bottlenecks
  • Optimize incrementally: Don't over-optimize prematurely
  • Test performance: Monitor your app's performance in production

Happy optimizing! 🚀