Computed

computed

Creates a reactive computed value that automatically updates when its dependencies change.

Signature

function computed<T>(getter: () => T): { readonly value: T };

Parameters

  • getter: A function that returns the computed value. This function is automatically tracked for dependencies.

Returns

An object with a readonly value property that contains the computed result.

Basic Usage

import { reactive, computed } from '@quantajs/core';

const state = reactive({
  count: 0,
  price: 10,
});

const doubleCount = computed(() => state.count * 2);
const totalPrice = computed(() => state.count * state.price);

console.log(doubleCount.value); // 0
console.log(totalPrice.value); // 0

state.count = 5;
console.log(doubleCount.value); // 10
console.log(totalPrice.value); // 50

Computed with Multiple Dependencies

Computed values can depend on multiple reactive sources:

const user = reactive({
  firstName: 'John',
  lastName: 'Doe',
  age: 25,
});

const fullName = computed(() => `${user.firstName} ${user.lastName}`);
const isAdult = computed(() => user.age >= 18);
const greeting = computed(() => 
  `Hello ${fullName.value}, you are ${isAdult.value ? 'an adult' : 'a minor'}`
);

console.log(greeting.value); // "Hello John Doe, you are an adult"

user.firstName = 'Jane';
console.log(greeting.value); // "Hello Jane Doe, you are an adult"

user.age = 16;
console.log(greeting.value); // "Hello Jane Doe, you are a minor"

Computed with Complex Logic

Computed values can handle complex calculations and transformations:

const todoStore = reactive({
  todos: [
    { id: 1, text: 'Learn QuantaJS', done: false, priority: 'high' },
    { id: 2, text: 'Build an app', done: true, priority: 'medium' },
    { id: 3, text: 'Deploy', done: false, priority: 'low' },
  ],
  filter: 'all', // 'all', 'active', 'completed'
});

const filteredTodos = computed(() => {
  switch (todoStore.filter) {
    case 'active':
      return todoStore.todos.filter(todo => !todo.done);
    case 'completed':
      return todoStore.todos.filter(todo => todo.done);
    default:
      return todoStore.todos;
  }
});

const todoStats = computed(() => {
  const todos = todoStore.todos;
  const total = todos.length;
  const completed = todos.filter(todo => todo.done).length;
  const active = total - completed;
  const completionRate = total === 0 ? 0 : (completed / total) * 100;
  
  return {
    total,
    completed,
    active,
    completionRate: Math.round(completionRate),
  };
});

const highPriorityTodos = computed(() => 
  todoStore.todos.filter(todo => todo.priority === 'high' && !todo.done)
);

console.log(filteredTodos.value.length); // 3
console.log(todoStats.value); // { total: 3, completed: 1, active: 2, completionRate: 33 }
console.log(highPriorityTodos.value.length); // 1

Performance Benefits

Computed values are cached and only recalculate when dependencies change:

const expensiveStore = reactive({
  items: Array.from({ length: 1000 }, (_, i) => ({ id: i, value: Math.random() })),
});

const expensiveCalculation = computed(() => {
  console.log('Computing expensive value...');
  return expensiveStore.items
    .map(item => item.value * 2)
    .reduce((sum, val) => sum + val, 0);
});

// First access - computes the value
console.log(expensiveCalculation.value); // "Computing expensive value..." then result

// Second access - uses cached value (no computation)
console.log(expensiveCalculation.value); // Uses cached value

// Only when items change does it recompute
expensiveStore.items.push({ id: 1000, value: 0.5 });
console.log(expensiveCalculation.value); // "Computing expensive value..." then new result

Computed in Stores

When using createStore, getters are automatically computed:

import { createStore } from '@quantajs/core';

const userStore = createStore('user', {
  state: () => ({
    users: [
      { id: 1, name: 'Alice', age: 25, role: 'admin' },
      { id: 2, name: 'Bob', age: 30, role: 'user' },
      { id: 3, name: 'Charlie', age: 35, role: 'admin' },
    ],
    filter: {
      minAge: 0,
      maxAge: 100,
      role: 'all',
    },
  }),
  getters: {
    filteredUsers: (state) => {
      return state.users.filter(user => {
        const ageMatch = user.age >= state.filter.minAge && user.age <= state.filter.maxAge;
        const roleMatch = state.filter.role === 'all' || user.role === state.filter.role;
        return ageMatch && roleMatch;
      });
    },
    averageAge: (state) => {
      if (state.users.length === 0) return 0;
      const total = state.users.reduce((sum, user) => sum + user.age, 0);
      return Math.round(total / state.users.length);
    },
    userStats: (state) => {
      const admins = state.users.filter(user => user.role === 'admin').length;
      const regularUsers = state.users.filter(user => user.role === 'user').length;
      return { admins, regularUsers, total: state.users.length };
    },
  },
  actions: {
    updateFilter(filter) {
      Object.assign(this.filter, filter);
    },
  },
});

console.log(userStore.filteredUsers.length); // 3
console.log(userStore.averageAge); // 30
console.log(userStore.userStats); // { admins: 2, regularUsers: 1, total: 3 }

userStore.updateFilter({ minAge: 30, role: 'admin' });
console.log(userStore.filteredUsers.length); // 1 (only Charlie)

Chaining Computed Values

Computed values can depend on other computed values:

const store = reactive({
  items: [1, 2, 3, 4, 5],
});

const sum = computed(() => store.items.reduce((a, b) => a + b, 0));
const count = computed(() => store.items.length);
const average = computed(() => count.value === 0 ? 0 : sum.value / count.value);
const variance = computed(() => {
  if (count.value === 0) return 0;
  const avg = average.value;
  const squaredDiffs = store.items.map(item => Math.pow(item - avg, 2));
  return squaredDiffs.reduce((a, b) => a + b, 0) / count.value;
});

console.log(sum.value); // 15
console.log(average.value); // 3
console.log(variance.value); // 2

store.items.push(6);
console.log(sum.value); // 21
console.log(average.value); // 3.5
console.log(variance.value); // 2.916...

Limitations

Side Effects

Computed values should be pure functions without side effects:

// ❌ Bad: Side effects in computed
const badComputed = computed(() => {
  console.log('This should not be in computed');
  document.title = 'Updated'; // Side effect
  return state.count * 2;
});

// ✅ Good: Pure computation
const goodComputed = computed(() => state.count * 2);

Async Operations

Computed values cannot be async. Use watchers for async operations:

// ❌ Bad: Async computed
const badComputed = computed(async () => {
  const result = await fetch('/api/data');
  return result.json();
});

// ✅ Good: Use watch for async operations
watch(() => state.id, async (id) => {
  const result = await fetch(`/api/users/${id}`);
  state.userData = await result.json();
});

Learn More