Publication isn't reactive

Hi. I have a publication that looks up the logged in user’s connection ids (an array user.connections) and returns a cursor of those Meteor.users to the client.

/**
 * Publish public fields of user connections.
 */
Meteor.publish('users.connections', function () {
  if (!this.userId) return this.ready();

    const user = Meteor.users.findOne(this.userId, { fields: { connections: 1 } });

    return Meteor.users.find({ _id: { $in: user.connections } }, { fields: { ...publicFields } });
});

It seems simple enough. But adding/removing a connection id to the logged in user.connections produces stale data on the client until the app is reloaded.

I can fix the issue by using the peerlibrary:reactive-publish package and surrounding the code with autorun.

/**
 * Publish public fields of user connections.
 */
Meteor.publish('users.connections', function () {
  if (!this.userId) return this.ready();

  this.autorun(function () {
    const user = Meteor.users.findOne(this.userId, { fields: { connections: 1 } });

    return Meteor.users.find({ _id: { $in: user.connections } }, { fields: { ...publicFields } });
  });
});

However this causes the publication to run upwards of 30 times when a user subscribes and logs in. When deploying to production the Galaxy server cpu runs at 100% and freezes for a few minutes while all logged in users are presumably updating pub/sub. So I’m trying to figure out a correct basic Meteor way of doing this without the need for the reactive package.

Here is the client subscription code for reference:

  /**
   * Subscriptions
   */
  const connectionsReady = useTracker(() => {
    const handle = Meteor.subscribe('users.connections');
    return handle.ready();
  }, []);

  /**
   * Reactive Data
   */
  const connections = useTracker(() => {
    return Meteor.users
      .find({ _id: { $ne: Meteor.userId() } }, { sort: { 'status.online': -1, username: 1 } })
      .fetch();
  }, []);

console.log(connections);
// ^ contains stale user data sometimes (i.e. i have 4 connections, i remove one
// on the client.. server/mongo updates correctly, this variable still shows 4 connections
// instead of 3).. reloading app shows 3 connections

Any help appreciated.

Hi, Meteor is going to be reactive in the cursor that you return, your first query is not going to be reactive as it’s not in the return of your publication.

Also, storing a dynamic array of ids is probably going to cause huge documents if your system grows, I would recommend that you create a collection called Connections to track your connections.

Doing that you could have a cursor (returned by your publication) as simple as Connections.find({userId: this.userId}) and you could get all the connection of your logged user.

It’s important to think about your models already with your publication in mind :wink:

1 Like

That was the missing piece! Thank you. I didn’t realize for whatever reason only the return part of a publication was reactive. Moving connections to a new collection sounds like best solution.

@filipenevola does something like this sound ideal to keep everything reactive?

  1. Publish new connections collection (of user._id’s)
  2. Subscribe on client then subscribe to users (passing connections array as param)
  3. In user publication, return cursor of users based on the passed connections id array

Almost there, if you provide a list of ids to your query again you are not going to have the reactivity that you want.

If a new user starts to add the other you are not going to have his id in the list.

1 - Your Connections collection should have at least 2 fields, like:
followerId, followingId so you know who is following who.
2 - Your publications should be as simple as:
“myFollowers” publication: Connections.find({followingId: this.userId})
“iAmFollowing” publication: Connections.find({followerId: this.userId})

This way you don’t need arrays at all and also you don’t need to send anything from the client as you already have the userId from the logged user in the server.

So when a new connection is added the live query (Meteor creates this for you automatically when you return a cursor) is going to see that there is a new followingId or followerId matching your query and this is going to be send to your client.

:wink:

Okay, thanks! The Connections collection only stores each Meteor.user _id, does it not?

So this:


Connections.find({followerId: this.userId})

Returns each _id and not each actual user object. Otherwise we’d be doubling up the Meteor.users collection data.

FWIW I can successfully make everything reactive. But it requires a second subsequent subscription:


Meteor.users.find({ _id: { $in: iAmFollowing } });

From my understanding useTracker re-runs on the client when the Connections publication/collection changes, which in turn re-runs the subscription to the Meteor.users publication (where iAmFollowing is passed as a param). But if this can be done in 1 pub/sub (returning the user object opposed to _id - without doubling up data) that would be super cool :sunglasses:

Hi, yes, one cursor is going to fetch the connections and other the users themselves.

You could use this package to return everything in the same publication: GitHub - Meteor-Community-Packages/meteor-publish-composite: Meteor.publishComposite provides a flexible way to publish a set of related documents from various collections using a reactive join or this one GitHub - peerlibrary/meteor-reactive-publish: Reactive publish endpoints

They have different ideas but both solve the same problem.