Vue Meteor Tracker
Vue Meteor Tracker is an integration library that seamlessly brings Meteor's powerful reactive data layer, based on `Tracker` computations, into Vue 3 and Vue 2.7+ applications. The current stable version is 2.0.0, which focuses on providing a more streamlined API compatible with modern Vue setups, including Vite. It allows developers to declare Meteor subscriptions and reactive data queries directly within Vue components' options API, ensuring that components automatically re-render when underlying Meteor collections or reactive sources change. A key differentiator is its automatic lifecycle management for Meteor subscriptions, stopping them when components are unmounted. This library simplifies building real-time UIs with Meteor and Vue by abstracting away manual Tracker computations and subscription management, offering a robust solution for reactive UIs in the Meteor ecosystem.
Common errors
-
TypeError: Cannot read properties of undefined (reading '$subReady')
cause The `VueMeteor` plugin was not properly installed (`app.use(VueMeteor)`), or an attempt to access `$subReady` occurred before the component was fully initialized.fixEnsure `app.use(VueMeteor)` is called early in your application's bootstrap. If calling `$subscribe` or accessing `$subReady` programmatically, ensure it's within an appropriate component lifecycle hook like `mounted()`. -
Error: [vue-meteor-tracker] The key 'myProp' in the meteor option is already defined in data, props, methods, etc. Please use a unique key.
cause A property name used within the `meteor` option (for reactive data) conflicts with an existing property in `data`, `props`, `methods`, or `computed` options (v2.0.0-beta.2 and later).fixRename the conflicting key in either your `meteor` option or the other component options (e.g., `data`, `props`) to ensure uniqueness. -
Subscription 'mySubscription' is not ready (i.e., this.$subReady.mySubscription is unexpectedly false) when using `meteor` option for subscriptions.
cause Incorrectly defining a subscription under the main `meteor` object instead of the `$subscribe` object, or passing `this.$subscribe` parameters as separate arguments instead of an array (v2.0.0-beta.3 and later).fixEnsure subscriptions are defined within the `meteor: { $subscribe: { ... } }` object. When calling `this.$subscribe`, pass parameters as an array: `this.$subscribe('my-sub', [1, 2, 3])` or as a function returning an array: `this.$subscribe('my-sub', () => [this.param])`. -
TypeError: (0, _threads_collection.Threads.findOne) is not a function
cause Attempting to use the `params` and `update` object structure for `meteor` data properties, which was removed in v2.0.0-beta.1.fixRefactor your `meteor` data property from `{ params() { ... }, update() { ... } }` to a single function that directly returns the reactive result: `myProp() { return Threads.findOne({ _id: this.selectedThreadId }); }`.
Warnings
- breaking In `v2.0.0-beta.3`, the `meteor: { subscribe: { ... } }` (without the `$` sign) option no longer functions as a subscription declaration and is instead treated as a regular data prop. Subscriptions must now be placed under the `$subscribe` key.
- breaking In `v2.0.0-beta.3`, the arguments for `this.$subscribe` have changed. Instead of variadic parameters, it now expects the publication name as a string and parameters as a single array or a function returning an array.
- breaking In `v2.0.0-beta.2`, an error is thrown if a key defined in the `meteor` option for reactive data already exists in `data`, `props`, `methods`, or `computed` options.
- breaking In `v2.0.0-beta.1`, data properties within the `meteor` option can now only be functions that directly return the reactive result. The previous `params` and `update` object structure is no longer supported.
Install
-
npm install vue-meteor-tracker -
yarn add vue-meteor-tracker -
pnpm add vue-meteor-tracker
Imports
- VueMeteor
const VueMeteor = require('vue-meteor-tracker')import { VueMeteor } from 'vue-meteor-tracker' - config
import config from 'vue-meteor-tracker'
import { config } from 'vue-meteor-tracker' - Component Options API
this.$meteor = { /* ... */ }export default { meteor: { $subscribe: { /* ... */ }, myReactiveData() { /* ... */ } } }
Quickstart
import { createApp } from 'vue';
import App from './App.vue';
import { VueMeteor } from 'vue-meteor-tracker';
// Mock Meteor for standalone demonstration if not in a Meteor environment.
// In a real Meteor app, `Meteor` and `Mongo.Collection` are globally available.
declare global {
namespace Meteor {
const subscribe: (name: string, ...args: any[]) => { ready: () => boolean };
const Collection: new (name: string) => { find: (query?: any) => { fetch: () => any[] }, findOne: (query?: any) => any };
}
}
if (typeof (globalThis as any).Meteor === 'undefined') {
(globalThis as any).Meteor = {
subscribe: (name: string, ...args: any[]) => {
console.log(`Mock Meteor: Subscribing to ${name} with params: ${JSON.stringify(args)}`);
return { ready: () => true }; // Always ready for mock
},
Collection: function(name: string) {
console.log(`Mock Meteor: Creating Collection ${name}`);
const data: any[] = [];
if (name === 'Notes') {
data.push({ _id: 'note1', text: 'First Note' });
data.push({ _id: 'note2', text: 'Second Note' });
}
if (name === 'Threads') {
data.push({ _id: 'thread1', title: 'Main Thread' });
}
return {
find: (query?: any) => ({ fetch: () => data }),
findOne: (query?: any) => data.find(item => Object.keys(query).every(key => item[key] === query[key])) || null
};
} as any
};
}
// Example Vue component (App.vue)
import { defineComponent, ref } from 'vue';
const Notes = (globalThis as any).Meteor.Collection('Notes');
const Threads = (globalThis as any).Meteor.Collection('Threads');
const AppVueComponent = defineComponent({
name: 'MeteorTrackerExample',
data() {
return {
selectedThreadId: 'thread1',
myReactiveParam: 'paramValue',
};
},
meteor: {
$subscribe: {
'notesSubscription': () => [],
'threadsSubscription': function() {
return [this.selectedThreadId];
},
},
notes: function() {
return Notes.find({}).fetch();
},
selectedThread: function() {
return Threads.findOne({ _id: this.selectedThreadId });
},
},
mounted() {
console.log('Component mounted. Initial notes:', this.notes);
this.$subscribe('anotherSub', ['arg1', this.myReactiveParam]);
},
template: `
<div>
<h1>Vue Meteor Tracker Example</h1>
<p v-if="!$subReady.notesSubscription">Loading notes...</p>
<div v-else>
<h2>Notes:</h2>
<ul>
<li v-for="note in notes" :key="note._id">{{ note.text }}</li>
</ul>
</div>
<p v-if="!$subReady.threadsSubscription">Loading thread...</p>
<div v-else>
<h2>Selected Thread:</h2>
<p v-if="selectedThread">{{ selectedThread.title }} (ID: {{ selectedThread._id }})</p>
<p v-else>No thread selected or found.</p>
</div>
<button @click="selectedThreadId = 'newThreadId'">Change Thread ID (mock)</button>
<p>Subscription 'anotherSub' ready: {{ $subReady.anotherSub }}</p>
</div>
`,
});
const app = createApp(AppVueComponent);
app.use(VueMeteor);
app.mount('#app');