Not returning a value wouldnât fix my problem. I have a weird edge-case situation with optimistic UI and shared user reactivity. Iâll explain - sorry for the length:
The Problem
I have a template SharedProductViewer that multiple users in different logged in sessions can collaboratively use to review a Product together. SharedProductViewer gets updated with a new set of Product data based on an index passed into it. The index is from an array of chosen grouped Products to review. ALL the Products are already loaded into minimongo on the client for SharedProductViewer. This helps with UI quickness when switching products.
When this product switch happens, it needs to reset a bunch of user-enabled product view states (e.g. render type, layers, etc.) that are controlled by additional mongo fields on a SharedProductViewer document that powers the SharedProductViewer template. The SharedProductViewer template needs to reset basically, but in the design, it does not get destroyed or recreated when switching the index, only ârehydratedâ with the new already loaded data (otherwise this wouldnât be a problem to reset the views).
Upon switching the index, the SharedProductViewer view state fields are reset on the server in a Collection hook. This is how they get reset.
When doing a Meteor Method call to updateIndex from the client, the index, like all the viewer fields, needs to be persisted to the database to drive reactivity from one user making an update and having it affect all other collaborative users looking at SharedProductViewer. It will indeed update the index and the SharedProductViewer template updates immediately due to optimistic UI (as all the Products are already loaded into minimongo). However, the reset views which are also tied to SharedProductViewer database fields, get updated in a Collection hook when the index gets updated. Basically the server cleaning up the view upon switching the Product index as there are multiple âviewsâ open by various logged in users collaborating. The viewerâs state isnât tied to a user session, itâs tied to the database (a very Meteor use-case BTW).
The problem is the UI flickers when the next Product is shown in the SharedProductViewer template because it momentarily shows the enabled views because they donât get reset until the Collection hook resets them.
I have an autorun that uses Template.currentData() to update the viewer reactively. This is the order of what happens:
- Meteor method call to client/server
updateIndex that updates the index field only.
- This causes optimistic UI to trigger the
autorun because it threads into Template.currentData(). This makes SharedProductViewer go to the next product with the unwanted views enabled before the server collection hook updates finish.
- The server Collection hooks run and reset all the views. This triggers the
autorun with Template.currentData() and resets the views.
Some Solutions
If there was a way to just call updateIndex with optimistic UI disabled, this problem wouldnât happen as all the updates would come back from the server together. As an interesting note, this problem only happens when a user updates the index from their client SharedProductViewer. If the update comes from another user, it works exactly right because all the updates are coming from the server together - i.e. thereâs no local optimistic UI update to index that flickers the view.
I could make updateIndex server only, but all the Products are loaded already in the client, so I want to take advantage of that and have the client update quickly. Iâd hate to lose optimistic UI and force everything to be server-only just to fix a UI flicker. 
I tried an approach using ReactiveVar to control the views enabled in SharedProductViewer and just reset the views locally before the Meteor Method call to updateIndex. Basically âcontrollingâ the UI myself manually on the client - and unhinging it from database fields. This doesnât work because due to reactivity and needing the SharedProductViewer to allow reactive updates from the server from other users. I have to tie those ReactiveVars to Template.currentData() to achieve server reactivity. This then kills this approach as optimistic UI will overwrite my manual attempts control the UI. When it goes to set index in Template.currentData() it will also overwrite all the view fields from the server that have not been updated yet.
Another approach is to simply add the view resets in the mongo updater in the client/server method updateIndex. This then âaddsâ them from the client making them set correctly for optimistic UI and it works perfectly. But, I donât really like this approach because this exactly duplicates what the server does and adds extra unwanted mongo code. And the Collection hooks catches it for ALL the various collaborative users when a Product index is updated.
Another approach is to just simply design it so it destroys the template and re-creates it. But this is also overkill and would cause overhead in the UI.
The solution I settled on builds on the above ReactiveVar solution and simply adds another ReactiveVar control boolean enableReactiveUpdates in the template. Before updateIndex is called, I disable enableReactiveUpdates. This bypasses the Template.currentData() call in the autorun. Then in the success callback of updateIndex I enable enableReactiveUpdates in a Tracker.afterFlush().
This approach momentarily stops Template.currentData() (as set by optimistic UI) from changing my manual resets to the UI. So the process looks like this now:
- Disable
enableReactiveUpdates causing no optimistic UI because Template.currentData() gets by passed in the autorun.
- Reset all the
ReactiveVar views and a ReactiveVar index that updates the UI instantly.
- Call
updateIndex (which is just to update the database and all other collaborative viewers).
- In the
updateIndex success callback, re-enable enableReactiveUpdates. This triggers the autorun and everything is synced up with the server.
This way still keeps the optimistic UI snappy, but doesnât let any delayed server field values from flickering the UI.
The gotcha is you have to use Tracker.afterFlush() in the success callback of updateIndex or else it will re-enable Template.currentData() too early. The flush makes sure everything from the updateIndex method call is updated to the template. This approach does however basically make it a server-only method.
The ultimate problem to put it simply, is Iâm trying to control what happens on the client, when a client update causes server side-effects that have an effect on the UI. Weâre years baked in our design at this point, but I will redesign all this at some point.