untracked

Read signals without creating reactive dependencies.

API

function untracked<T>(fn: () => T): T
  • fn: A function to execute without tracking signal dependencies.
  • Returns: The value returned by the function fn.

TypeScript

The untracked function is generic and preserves the return type of the function it wraps.

type UntrackedFunction<T> = () => T;

const value: number = untracked(() => {
  // read some signals...
  return 42;
});

Basic Usage

Use untracked to access a signal’s value inside a reactive scope (like computed or effect) without making that scope depend on the signal.

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

const value = signal(5);
const multiplier = signal(10);

const result = computed(() => {
  // `result` will update when `value` changes...
  const currentValue = value();

  // ...but it will NOT update when `multiplier` changes.
  const currentMultiplier = untracked(() => multiplier());
  
  return currentValue * currentMultiplier;
});

console.log(result()); // 50

value(6);
console.log(result()); // 60 (recomputed because `value` is a dependency)

multiplier(20);
console.log(result()); // 60 (NOT recomputed because `multiplier` is untracked)

Key Concepts

Dependency Isolation

Creates a reactive boundary where signals can be read without establishing dependency relationships with the calling context.

const tracked = signal('tracked');
const untracked = signal('untracked');
const result = computed(() => {
  return tracked() + untracked(() => untracked());
}); // Only depends on 'tracked', not 'untracked'

Selective Tracking

Enables fine-grained control over which signals trigger reactive updates, preventing unnecessary recalculations.

const data = signal('data');
const debugMode = signal(false);
effect(() => {
  console.log(data()); // This creates a dependency
  if (untracked(() => debugMode())) { // This doesn't
    console.log('Debug info');
  }
});

Debug-Safe Reading

Essential for debug logging and conditional features that shouldn’t influence reactive behavior.

const computation = computed(() => {
  const result = expensiveCalculation();
  if (untracked(() => debugEnabled())) {
    console.log('Result:', result); // Debug doesn't affect reactivity
  }
  return result;
});

Performance Optimization

Prevents unwanted dependencies that could cause expensive computations to run more frequently than necessary.

const counter = signal(0);
const metadata = signal({ version: 1 });
const optimized = computed(() => {
  const count = counter();
  const version = untracked(() => metadata().version); // No dependency on metadata
  return { count, version };
});

Important Considerations

Overusing untracked

Most of the time, you want dependencies to be tracked automatically. Only use untracked when you have a specific reason to prevent a dependency.

// ❌ Breaking necessary reactivity
const result = computed(() => untracked(() => signal()));
// ✅ Only untrack what shouldn't be reactive
const result = computed(() => signal() + untracked(() => debugValue()));

Debug Code Dependencies

Always wrap debug-only signal reads in untracked to avoid unwanted dependencies.

// ❌ debugMode creates unwanted dependency
const c = computed(() => {
  if (debugMode()) console.log('calculating...');
  return expensiveCalculation();
});
// ✅ Debug code doesn't affect reactivity
const c = computed(() => {
  if (untracked(() => debugMode())) console.log('calculating...');
  return expensiveCalculation();
});