Non reactive update for optimistic UI


#1

I’m using withTracker on a collection of user lists. The user has the ability of reordering the lists through the UI which updates the specific item in the collection through the meteor method. This then triggers withTracked to fire with the new data causing a re-render in a case where the UI is already showing the correct state. Is it possible to make withTracker not fire for particular updates? I need this to reduce the number of renders without having to compare variables

// Server

Meteor.publish('lists', function (userId) {
  return Lists.find({ userId: Meteor.userId() });
});

...


Meteor.methods({
  "lists.setorder"({ listId, order}) {
     return Lists.update(listId, { $set: { order } }
  }
})

// Client


export default withTracker(props => {
    const listsHandle = Meteor.subscribe("lists");
    const lists = Lists.find({}).fetch();
    return { 
       ready: listsHandle.ready(),  
       lists 
    }
})(ListView)

...

// After the lists are re-ordered in the UI I call this to update the state in the backend:
Meteor.call("lists.setorder", ....

#2

Hmmm, interesting scenario.

Questions:

  1. Can you give us an idea of your document schema? i.e. Is the order field a top level property or is it nested?
  2. Can you give us the code for your publication? Mainly, is the UI ordering the list based on the order of the subscription return (via a sort property for example)?

#3

Are you sure it re-renders?
I thought that a Tracker only re-runs the function if the reactive value changes (returns false to a comparison function)


#4

I’ll be honest and say that this is a really confusing question.

It sounds like your goal is to finely control when a list reordering occurs in order to reduce choppiness you experience in the client.

lists.setOrder as it’s written is really fishy. The withTracker call doesn’t perform a sort on the order key. That suggests you’re doing some kind of sort in your component, so the only graphics-update-preventing affordances are provided by React, not Meteor.

If you want meteor's affordances for reducing re-renders of list items, use blaze.

If you want to solve this problem with react you should follow the guidelines here: https://blog.logrocket.com/rendering-large-lists-with-react-virtualized-82741907a6b3

Unfortunately out of the box react provides no affordances for rendering lists efficiently like you expect from blaze, but there are great packages like react-virtualized you can try.


#5

@hemalr87 :

  1. order is a top level property.
  2. lists are rendered after being sorted by the order field which is a float

@coagmano :

The tracker fires any time there is a change to the relevant collection items. I even tried removing the field from the publish function:

Meteor.publish('lists', function (userId) {
  return Lists.find({ userId: Meteor.userId() }, { fields: { order: 0 }  });
});

@doctorpangloss

There is nothing fishy about lists.setOrder… order is a float :slightly_smiling_face:

I’m rendering many other nested components to the point where performance is affected by unnecessary re-renders upstream.
I’ve tried debouncing/throttling meteor updates in the beginning but now I’ve come to a point where I need to exclude unnecessary fields from pub/sub.
Before I create a separate collection for these untracked fields I wanted to check with more experienced people to see if there is a way to avoid splitting a collection


#6

So you don’t want the UI to re-render if the update is triggered by the same user (since their UI is already up to date) yet you want to re-render for other users (hence the subscription).

Tracker only knows that the collection being observed has been updated but it doesn’t know who updated it. So unless you track the source of change, how would tracker/component know whether to update or not? If you’re using react, then at the parent you can skip the render by comparing the prev/new orders. The other way is to subscribe to some sort of control doc that has author info in the DB that triggers on update, and have a method to fetch the new data if the change author is different than the logged in user…


#7

Thanks @alawi, that’s what I was thinking about the tracker. I’ll focus on the react life-cycle functions


#8

What I’m trying to figure out is whether the sorting is happening via a mongo query or outside of it.

If inside a query, then perhaps (I’m not sure), it would help if you moved it outside so that React is doing the sorting for you…?


#9

If the sorting is removed outside mongo, then other users won’t know and that would defeat the purpose of subscription in the first place. I’m guessing the feature is some sort of shared drag and drop ordered list.


#10

No no, the order property should still remain in the database, and update accordingly (so that other users know).

But, instead of using a $sort in the mongo query to determine the order in which this items are rendered, the client side JS could instead arrange the list. The untested theory I have in my mind is that React will recognise when the order has changed via the props update, and render the new order accordingly (i.e. - only for the user not making the change).

Although now that I type all that out, the props will still change and force even the react side order calculation to re-render… :confused:Hmm, I dno…


#11

I think storing the source of last update as you suggested may be the easiest/most straightforward implementation… @alawi


#12

The order is worked out by sorting the lists on the client side using the order value.

Each user only sees his list.

I’ve been using withTracker because most of the other changes to the list items are not that frequent and it has been going fine so far.
However, some changes related to the layout are becoming very frequent and the UI needs to be more responsive


#13

I’m trying to tell you that the best way to do that is by using react-virtualized or an equivalent, because (1) by using fetch with no fields specifier on your find, you react to all changes and (2) that causes a complete re-render of the list for every change inside a logical row.

So what you think is a change unrelated to the layout is, by definition of the code, related to the layout, since the application doesn’t know the difference! In blaze it does!