Computed Values#

Computed values automatically derive from other signals and update when their dependencies change. They are lazy (only compute when accessed) and cached (memoize results).

Creating Computed Values#

Access computed values using .value, just like primitive signals:

TSX
import { signal, computed } from 'sigx';

const firstName = signal({ value: 'John' });
const lastName = signal({ value: 'Doe' });

// Access computed via .value
const fullName = computed(() => {
    return `${firstName.value} ${lastName.value}`;
});

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

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

Computed with Object Signals#

When using object signals (the recommended pattern), access properties directly:

TSX
import { signal, computed } from 'sigx';

const state = signal({
    firstName: 'John',
    lastName: 'Doe'
});

const fullName = computed(() => {
    return `${state.firstName} ${state.lastName}`;
});

console.log(fullName.value);  // "John Doe"
state.firstName = 'Jane';
console.log(fullName.value);  // "Jane Doe"

Computed in Components#

TSX
import { component, computed, render } from 'sigx';

const ShoppingCart = component(({ signal }) => {
    const state = signal({
        items: [
            { name: 'Apple', price: 1.50, qty: 3 },
            { name: 'Banana', price: 0.75, qty: 5 },
        ]
    });
    
    // Computed values
    const total = computed(() => {
        return state.items.reduce(
            (sum, item) => sum + item.price * item.qty, 
            0
        );
    });
    
    const itemCount = computed(() => {
        return state.items.reduce((sum, item) => sum + item.qty, 0);
    });
    
    return () => (
        <div>
            <p>{itemCount.value} items</p>  {/* Access via .value */}
            <p>Total: ${total.value.toFixed(2)}</p>
        </div>
    );
});

render(<ShoppingCart />, "#sandbox");

Writable Computed#

Create computed values with custom setters. Set values using .value =:

TSX
import { signal, computed } from 'sigx';

const state = signal({ firstName: 'John', lastName: 'Doe' });

const fullName = computed({
    get: () => `${state.firstName} ${state.lastName}`,
    set: (value: string) => {
        const [first, last] = value.split(' ');
        state.firstName = first;
        state.lastName = last || '';
    }
});

console.log(fullName.value);  // "John Doe"
fullName.value = 'Jane Smith';  // Set via .value
console.log(state.firstName);  // "Jane"

Computed vs Effects#

ComputedEffect
Returns a value via .valueRuns side effects
Lazy - only runs when accessedEager - runs immediately
Cached - memoizes resultNo caching
Pure - no side effectsCan have side effects
TSX
// ✅ Use computed for derived values
const doubled = computed(() => state.count * 2);
console.log(doubled.value);  // Access via .value

// ✅ Use effect for side effects
effect(() => {
    document.title = `Count: ${state.count}`;
});

Chaining Computed Values#

Computed values can depend on other computed values:

TSX
import { signal, computed } from 'sigx';

const state = signal({ a: 1, b: 2 });

const sum = computed(() => state.a + state.b);
const doubled = computed(() => sum.value * 2);  // Access via .value

console.log(doubled.value);  // 6

state.a = 5;
console.log(doubled.value);  // 14

Type Guard#

Use isComputed() to check if a value is a computed signal:

TSX
import { computed, isComputed } from 'sigx';

const doubled = computed(() => 42);

console.log(isComputed(doubled));       // true
console.log(isComputed({ value: 1 }));  // false

Performance#

Computed values offer significant performance benefits:

  • Lazy evaluation: Only computes when the value is actually read
  • Dependency tracking: Automatically knows which signals to watch
  • Caching: Won't recompute if dependencies haven't changed
  • Minimal updates: Only dependent effects/computeds update

Computed values are:

  • Lazy - Only calculated when accessed
  • Cached - Result is memoized until dependencies change
  • Efficient - Fine-grained dependency tracking

Next Steps#