Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/meteor/meteor/llms.txt

Use this file to discover all available pages before exploring further.

Tracker is Meteor’s incredibly lightweight (~1k) library for transparent reactive programming. It allows reactive data sources to automatically notify reactive consumers without manual event handling.

Installation

meteor add tracker
Tracker is included by default in Meteor applications and is the foundation of Meteor’s reactivity system.

Overview

Tracker provides:
  • Reactive Computations - Automatically rerun code when dependencies change
  • Dependency Tracking - Transparent detection of data dependencies
  • Automatic Cleanup - Proper disposal of reactive contexts
  • Minimal Overhead - Nearly zero cost when not in reactive context
  • Simple API - Just a few functions to learn

Package Information

  • Version: 1.3.4
  • Summary: Dependency tracker to allow reactive callbacks
  • Exports: Tracker, Deps (deprecated alias)
  • Size: ~1KB minified

Core Concepts

Reactive Context

A reactive context is created by Tracker.autorun. Code inside this context automatically tracks dependencies:
// This creates a reactive context
Tracker.autorun(() => {
  // Any reactive data source accessed here is tracked
  const user = Meteor.user();
  console.log('Current user:', user?.username);
});
// Prints immediately, then again whenever Meteor.user() changes

Dependencies

Reactive data sources use Tracker.Dependency to notify computations:
const temperature = 72;
const tempDep = new Tracker.Dependency();

function getTemperature() {
  tempDep.depend(); // Register as dependency
  return temperature;
}

// Later, when temperature changes:
temperature = 75;
tempDep.changed(); // Notify all dependent computations

API Reference

Tracker.autorun()

Run a function now and rerun whenever dependencies change:
const computation = Tracker.autorun(() => {
  const count = Session.get('counter');
  console.log('Count:', count);
});

// Stop the computation
computation.stop();
runFunc
function
required
Function to run in reactive context. Receives the Computation object.
options
object
Optional configuration
options.onError
function
Error handler called if the computation throws

Advanced autorun

Tracker.autorun((computation) => {
  // Access computation object
  console.log('First run?', computation.firstRun);
  
  const data = ReactiveVar.get();
  
  if (computation.firstRun) {
    // Setup code that only runs once
    console.log('Initializing...');
  }
  
  // Stop self after 10 runs
  if (runCount++ > 10) {
    computation.stop();
  }
}, {
  onError: (error) => {
    console.error('Computation error:', error);
  }
});

Tracker.Computation

Represents a reactive computation:
const computation = Tracker.autorun(() => {
  // Computation code
});

// Properties
computation.stopped    // Boolean: has been stopped?
computation.invalidated // Boolean: has been invalidated?
computation.firstRun   // Boolean: is this the first run?

// Methods
computation.stop()          // Stop this computation
computation.invalidate()    // Invalidate and rerun
computation.onInvalidate(callback) // Run callback on invalidate
computation.onStop(callback)       // Run callback when stopped

Lifecycle Callbacks

const computation = Tracker.autorun((comp) => {
  const data = getData();
  
  // Run when computation invalidates
  comp.onInvalidate(() => {
    console.log('About to rerun');
    cleanup();
  });
  
  // Run when computation stops
  comp.onStop(() => {
    console.log('Computation stopped');
    finalCleanup();
  });
});

Tracker.Dependency

Create reactive data sources:
class ReactiveCounter {
  constructor() {
    this._value = 0;
    this._dep = new Tracker.Dependency();
  }
  
  get() {
    this._dep.depend(); // Register dependency
    return this._value;
  }
  
  set(value) {
    this._value = value;
    this._dep.changed(); // Notify dependents
  }
  
  increment() {
    this.set(this._value + 1);
  }
}

const counter = new ReactiveCounter();

Tracker.autorun(() => {
  console.log('Counter:', counter.get());
});
// Prints: Counter: 0

counter.increment();
// Prints: Counter: 1

Dependency Methods

const dep = new Tracker.Dependency();

dep.depend()           // Register current computation as dependent
dep.depend(computation) // Register specific computation
dep.changed()          // Invalidate all dependent computations
dep.hasDependents()    // Check if any computations depend on this

Tracker.active

Check if currently in reactive context:
function reactiveFunction() {
  if (Tracker.active) {
    // We're inside a reactive computation
    dependency.depend();
  }
  return someValue;
}

Tracker.currentComputation

Get the current computation:
function myReactiveSource() {
  const computation = Tracker.currentComputation;
  
  if (computation) {
    // Register with current computation
    dependency.depend();
    
    computation.onInvalidate(() => {
      console.log('This computation is invalidating');
    });
  }
  
  return value;
}

Tracker.nonreactive()

Run code without establishing dependencies:
Tracker.autorun(() => {
  const reactive = Session.get('reactive');
  
  // This won't create a dependency
  const nonReactive = Tracker.nonreactive(() => {
    return Session.get('other');
  });
  
  console.log(reactive, nonReactive);
  // Only reruns when 'reactive' changes, not 'other'
});

Tracker.flush()

Flush pending reactive updates immediately:
// Invalidate computation
someDependency.changed();

// Normally, rerun happens later
// Force immediate rerun:
Tracker.flush();
Use Tracker.flush() sparingly. It can cause performance issues and unexpected behavior.

Tracker.afterFlush()

Run callback after next flush:
Tracker.afterFlush(() => {
  console.log('All reactive updates complete');
});

Patterns and Best Practices

Pattern: Reactive Data Source

class ReactiveValue {
  constructor(initialValue) {
    this._value = initialValue;
    this._dep = new Tracker.Dependency();
  }
  
  get() {
    if (Tracker.active) {
      this._dep.depend();
    }
    return this._value;
  }
  
  set(newValue) {
    if (this._value !== newValue) {
      this._value = newValue;
      this._dep.changed();
    }
  }
}

Pattern: Cleanup Resources

Tracker.autorun((computation) => {
  const data = fetchData();
  const subscription = subscribeToUpdates(data.id);
  
  // Clean up subscription when computation reruns or stops
  computation.onInvalidate(() => {
    subscription.stop();
  });
  
  updateUI(data);
});

Pattern: Conditional Dependencies

Tracker.autorun(() => {
  const mode = Session.get('mode');
  
  if (mode === 'admin') {
    // Only depends on adminData when in admin mode
    const adminData = Session.get('adminData');
    renderAdmin(adminData);
  } else {
    const userData = Session.get('userData');
    renderUser(userData);
  }
});

Pattern: Throttling Reactivity

let throttleTimeout = null;
const throttledDep = new Tracker.Dependency();

function throttledChanged() {
  if (!throttleTimeout) {
    throttleTimeout = setTimeout(() => {
      throttledDep.changed();
      throttleTimeout = null;
    }, 1000); // Update at most once per second
  }
}

Pattern: Preventing Infinite Loops

let updating = false;

Tracker.autorun(() => {
  if (updating) return;
  
  const value = reactiveSource.get();
  
  updating = true;
  processValue(value); // Might trigger reactivity
  updating = false;
});

Integration with Meteor

Reactive Data Sources in Meteor

Many Meteor APIs are Tracker-aware:
Tracker.autorun(() => {
  // All of these are reactive:
  const user = Meteor.user();           // Current user
  const userId = Meteor.userId();       // Current user ID
  const loggingIn = Meteor.loggingIn(); // Login state
  const status = Meteor.status();       // Connection status
  
  const todos = Todos.find().fetch();   // Database query
  const count = Todos.find().count();   // Query count
  
  const setting = Session.get('key');   // Session variable
  
  console.log('Something changed!');
});

Using with Blaze Templates

Blaze automatically creates reactive contexts:
<!-- template.html -->
<template name="todoCount">
  <p>You have {{count}} todos</p>
</template>
// template.js
Template.todoCount.helpers({
  count() {
    // Automatically reactive - no Tracker.autorun needed
    return Todos.find({ userId: Meteor.userId() }).count();
  }
});

Using with React

import { useTracker } from 'meteor/react-meteor-data';

function TodoList() {
  const { todos, loading } = useTracker(() => {
    const handle = Meteor.subscribe('todos');
    
    return {
      todos: Todos.find({}, { sort: { createdAt: -1 } }).fetch(),
      loading: !handle.ready()
    };
  }, []);
  
  if (loading) return <div>Loading...</div>;
  
  return (
    <ul>
      {todos.map(todo => <li key={todo._id}>{todo.text}</li>)}
    </ul>
  );
}

Example: Temperature Monitor

// Create a reactive temperature source
class Thermometer {
  constructor() {
    this._temperature = 20;
    this._dep = new Tracker.Dependency();
    
    // Simulate temperature changes
    setInterval(() => {
      this.setTemperature(this._temperature + Math.random() - 0.5);
    }, 1000);
  }
  
  read() {
    this._dep.depend();
    return this._temperature;
  }
  
  setTemperature(temp) {
    this._temperature = temp;
    this._dep.changed();
  }
}

const thermometer = new Thermometer();

// Helper function (reactive)
function temperatureInFahrenheit() {
  return thermometer.read() * 9/5 + 32;
}

// Monitor temperature
const computation = Tracker.autorun(() => {
  const tempF = temperatureInFahrenheit();
  console.log(`Temperature: ${tempF.toFixed(1)}°F`);
  
  if (tempF < 32) {
    console.log('⚠️ Below freezing!');
  }
});

// Stop monitoring after 10 seconds
setTimeout(() => computation.stop(), 10000);

Example: Smart Cache

class ReactiveCache {
  constructor() {
    this._cache = new Map();
    this._deps = new Map();
  }
  
  get(key) {
    // Create dependency for this key if needed
    if (!this._deps.has(key)) {
      this._deps.set(key, new Tracker.Dependency());
    }
    
    // Register dependency
    this._deps.get(key).depend();
    
    return this._cache.get(key);
  }
  
  set(key, value) {
    this._cache.set(key, value);
    
    // Invalidate only computations watching this key
    const dep = this._deps.get(key);
    if (dep) {
      dep.changed();
    }
  }
  
  delete(key) {
    this._cache.delete(key);
    const dep = this._deps.get(key);
    if (dep) {
      dep.changed();
    }
  }
}

const cache = new ReactiveCache();

// Watch specific cache key
Tracker.autorun(() => {
  const value = cache.get('user:123');
  console.log('User 123:', value);
});

// This triggers the autorun
cache.set('user:123', { name: 'Alice' });

// This doesn't (different key)
cache.set('user:456', { name: 'Bob' });

Debugging

Log Computation Lifecycle

Tracker.autorun((computation) => {
  console.log('Running computation', computation);
  
  computation.onInvalidate(() => {
    console.log('Computation invalidated');
  });
  
  computation.onStop(() => {
    console.log('Computation stopped');
  });
  
  // Your reactive code
  const data = getData();
});

Find Reactive Dependencies

let dependencies = [];

const originalDepend = Tracker.Dependency.prototype.depend;
Tracker.Dependency.prototype.depend = function() {
  dependencies.push(this);
  return originalDepend.apply(this, arguments);
};

Tracker.autorun(() => {
  dependencies = [];
  myReactiveFunction();
  console.log('Dependencies:', dependencies.length);
});

Performance Tips

Minimize computation work - Keep autoruns lightweight
Use Tracker.nonreactive() - Avoid unnecessary dependencies
Stop unused computations - Always call .stop() when done
Batch updates - Invalidate multiple dependencies before flushing

reactive-var

Single reactive values

reactive-dict

Reactive key-value dictionaries

mongo

Reactive database queries

session

Global reactive state (deprecated)

Source Code