computed

A read-only signal that automatically updates when dependencies change.

API

function computed<T>(getter: (previousValue?: T) => T): () => T
  • getter: A function that computes the value. It receives the previously computed value as an optional argument.
  • Returns: A read-only signal.

TypeScript

The computed function infers its type from the getter’s return value.

import { signal, computed, ReadonlySignal } from '@hellajs/core';

const count = signal(0);

// Type is inferred as ReadonlySignal<number>
const double = computed(() => count() * 2);

// Explicit typing
type User = { firstName: string; lastName: string };
const user = signal<User>({ firstName: 'John', lastName: 'Doe' });
const fullName = computed<string>(() => `${user().firstName} ${user().lastName}`);

// The ReadonlySignal<T> type represents the returned function
const derivedValue: ReadonlySignal<string> = computed(() => `Count: ${count()}`);

Basic Usage

Computed signals update automatically when any of their dependencies (signals read during execution) change.

import { signal, computed } from '@hellajs/core';

const firstName = signal("John");
const lastName = signal("Doe");
const fullName = computed(() => `${firstName()} ${lastName()}`);

console.log(fullName()); // "John Doe"

firstName("Jane");
console.log(fullName()); // "Jane Doe"

Key Concepts

Lazy Evaluation

Computed signals are lazy. They only recompute when their value is requested and a dependency has changed.

const a = signal(1);
const b = signal(2);

const sum = computed(() => {
  console.log('Computing sum...');
  return a() + b();
});

// Computation does not run if the value is not accessed
a(10);

// Computation runs now, because sum() is accessed
console.log(sum()); // Logs: "Computing sum..." then "12"

// Accessing again returns the cached value without recomputing
console.log(sum()); // "12"

Chaining Computations

Computed signals can depend on other computed signals, creating an efficient graph of reactive dependencies.

const price = signal(100);
const quantity = signal(2);

const subtotal = computed(() => price() * quantity());
const tax = computed(() => subtotal() * 0.07); // 7% tax
const total = computed(() => subtotal() + tax());

console.log(total()); // 214

quantity(3);
console.log(total()); // 321

Conditional Dependencies

A computed signal only depends on the signals that are actually read during its last execution.

const showAdvanced = signal(false);
const basic = signal('basic');
const advanced = signal('advanced');

const setting = computed(() => {
  if (showAdvanced()) {
    return `Advanced: ${advanced()}`;
  }
  return `Basic: ${basic()}`;
});

// Initially, `setting` only depends on `showAdvanced` and `basic`.
// Changing `advanced` will not trigger a re-computation.
advanced('new advanced'); 
console.log(setting()); // "Basic: basic"

// After this, `setting` depends on `showAdvanced` and `advanced`.
showAdvanced(true);
console.log(setting()); // "Advanced: new advanced"

Previous Value Optimization

The getter function receives the previously computed value as its first argument, enabling incremental calculations and performance optimizations.

const numbers = signal([1, 2, 3]);

const sum = computed((previousSum) => {
  const currentNumbers = numbers();
  
  // Optimization: if we have a previous sum, we can potentially use it
  if (previousSum !== undefined) {
    // For this example, just do full computation
    // Real optimizations would need to track previous state
    return currentNumbers.reduce((acc, n) => acc + n, 0);
  }

  // Full computation
  return currentNumbers.reduce((acc, n) => acc + n, 0);
});

Important Considerations

Keep Getters Pure

The getter function should not have side effects. Use effect for side effects.

// ❌ Side effects in computed
const result = computed(() => {
  fetch('/api/data');
  return count() * 2;
});
// ✅ Pure computation only
const doubled = computed(() => count() * 2);

Avoid Complex Logic

Break complex computations into smaller, chained computed signals for better performance and debugging.

// ✅ Split complex logic
const filtered = computed(() => data().filter(item => item.active));
const sorted = computed(() => [...filtered()].sort());