The problem
Both Meteor and Vue have their own systems that allow you to create and manage reactive data. In Meteor, client side database queries are reactive, for example. In Vue 3, data held in ref() is reactive, among other (Vue or Pinia) sources. Using Meteor and Vue together in an app means making these two reactivity systems understand each other so they work together.
In practice for me this means keeping most of the app’s front end state in Vue’s own reactivity system so that it is easy to manage data inside and between components with the tools that Vue itself provides (ref(), props etc). Meteor’s role is to inject external data into Vue where necessary, primarily data coming from the database. With Meteor methods it is simple, on client side you get a response to your method call and you do whatever you like with that data, usually save it in a ref() or some other Vue reactive variable. But when you want to use Meteor’s pub/sub and real time live queries in your front end, a few more steps are involved.
A classic situation is as follows. You have a “ClientDetails” component in Vue that receives a “clientId” prop from a parent component. When clientId exists, you want to:
- subscribe to a “clientDetails” publication with the clientId, so that Meteor sends you that client’s data
- make a live query to minimongo on the front end in a wat that any changes to that data in the database are reflected in Vue’s state
And of course the clientId prop can change at any time as a result of some user action, so whenever clientId changes, a new subscription has to be made and a new client side database query needs to be done. So you can’t just statically run a Meteor.subscribe and Clients.findOne({}) in your client side code.
Solution in Vue 2
In short, the solution in Vue 2 has been to use the vue-meteor-tracker npm package. The package allows you to create Meteor subscriptions and local live database queries that use Vue’s reactive variables and automatically get rerun when those variables change. It has worked well.
Solution in Vue 3
Akryum did update his vue-meteor-tracker package to work with Vue 3. However, the package was last updated 2 years ago and is still marked as beta. And when I recently started to update a Meteor 2/Vue2 project to Meteor 3/Vue 3 I ran into issues with it. Subscriptions worked fine, but for some reason live database queries returned data in an unexpected format. Very likely some mistake on my part. However, given the above, I opted not to look very deep into it and rather chose to use Meteor’s tools directly to achieve a similar result.
Below I’ll show code for a component that shows real time data for a client and a project. It uses Vue 3’s composition API. A clientId is kept in a Vue computed variable. Using a Vue watch(), when the clientId changes, a new Meteor subscription is made and also a live database query is made. Both the subscription and database query are executed inside a Tracker autorun to make sure these update when data changes. When the clientId changes and therefore the relevant subscription and database query get rerun, the old trackers are stopped. The trackers are also stopped when the whole component is unmounted. Exactly the same applies for projectId.
const clientId = computed(() => {
// return clientId from other data
});
const projectId = computed(() => {
// return projectId from other data
});
// SUBSCRIPTION CLIENT
let trackerSubClient = undefined;
let subClient = undefined;
const subClientReady = ref(false);
watch(clientId, (to, from) => {
if (trackerSubClient) trackerSubClient.stop();
if (subClient) subClient.stop();
if (to) makeSubClient();
}, { immediate: true });
function makeSubClient() {
trackerSubClient = Tracker.autorun(() => {
subClient = Meteor.subscribe('clientForTime', clientId.value);
subClientReady.value = subClient.ready();
});
}
// /SUBSCRIPTION CLIENT
// LIVE QUERY CLIENT
let trackerQueryClient = undefined;
watch(clientId, (to, from) => {
if (trackerQueryClient) trackerQueryClient.stop();
makeQueryClient();
}, { immediate: true });
function makeQueryClient() {
trackerQueryClient = Tracker.autorun(() => {
client.value = Clients.findOne({ _id: clientId.value });
});
}
// /LIVE QUERY CLIENT
// SUBSCRIPTION PROJECT
let trackerSubProject = undefined;
let subProject = undefined;
const subProjectReady = ref(false);
watch(projectId, (to, from) => {
if (trackerSubProject) trackerSubProject.stop();
if (subProject) subProject.stop();
if (to) makeSubProject();
}, { immediate: true });
function makeSubProject() {
trackerSubProject = Tracker.autorun(() => {
subProject = Meteor.subscribe('projectForTime', projectId.value);
subProjectReady.value = subProject.ready();
});
}
// /SUBSCRIPTION PROJECT
// LIVE QUERY PROJECT
let trackerQueryProject = undefined;
watch(projectId, (to, from) => {
if (trackerQueryProject) trackerQueryProject.stop();
makeQueryProject();
}, { immediate: true });
function makeQueryProject() {
trackerQueryProject = Tracker.autorun(() => {
project.value = Projects.findOne({ _id: projectId.value });
});
}
// /LIVE QUERY PROJECT
// METEOR CLEANUP
onUnmounted(() => {
if (trackerSubClient) trackerSubClient.stop();
if (trackerQueryClient) trackerQueryClient.stop();
if (trackerSubProject) trackerSubProject.stop();
if (trackerQueryProject) trackerQueryProject.stop();
if (subClient) subClient.stop();
if (subProject) subProject.stop();
});
// /METEOR CLEANUP
Hopefully the above code is helpful to somebody. It is more verbose than using vue-meteor-tracker. But it does show that you can make things work without an external dependency by just using Vue’s watch() and Meteor’s Tracker. It does require you to use Vue 3’s composition API though.
In general, working with Meteor 3 and Vue 3 has been a pleasure so far. I encourage anyone to jump right in!
By the way, a first version of the Vue 3 tutorial is being written by @fredmaiaarantes at Meteor.js 3 + Vue 3 Tutorial by fredmaiaarantes · Pull Request #13279 · meteor/meteor · GitHub. It seems that there as well the plan is to remove the vue-meteor-tracker package.