Optimistic UI - flash of old data when get response from server

I’ve been suffering from a very weird problem for ages and will happily buy a pint for anyone who helps me solve it! :beer:

My in/out board app shows a team of users and their current location & return time. When you click on a user’s button their status toggles between in/out. Simple. Users can change the status of all other users within the same team.

The problem is, when the user clicks on their own status, about 1 time out of 5 it will update instantly but then there will be flash of the previous status when the method returns from the server. To take advantage of optimistic UI the method is defined on both the client and the server (all data validation, permission checking, etc, removed, but this stripped method still has the bug):

Meteor.methods({
    'users.setStatus': function (id, out, status, time, millisecsTillReturn, teamId) { 
        console.log('users.setStatus', Meteor.user().profile.name, this.userId, id, out, status, time, millisecsTillReturn, teamId);
        const update={
            $set: {
                "status.out":out,
                "status.status":status
            }
        };
        Meteor.users.update(id, update); // my TODO why is this causing flicker on production on own status if done on client as well ???
    },
});

On that ~1 time out of 5, the status changes instantly thanks to optimistic UI, but then when the server version of the method completes the status quickly flashes back to the old status, then back to the new. I can put a delay on the server method with:

        if (Meteor.isServer) Meteor._sleepForMs(2000);

And the flash comes 2 seconds after the you click on the status.

Here’s a screenshot of the websocket:

This shows three good toggles of the status and then one bad toggle with flash. On the good toggles the messages are in the order method, result, changed, updated but on the bad toggle the updated and changed messages are switched. Weird.

This only happens when clicking on your own status, so I wonder if it’s because the Meteor.user() data is being updated as well as the users_in_team subscription?? No other subscriptions publish the status field of the users collection. It only happens about 1/5 to 1/10 of the time, but for some reason it happens more often if I run meteor with the --production flag.

I’m not even defining the callback of Meteor.call (using viewmodel/react):

    toggleOut(e) {
        const user=this.user();
        Meteor.call("users.setStatus", user._id, !user.status.out, user.status.out ? '' : 'Out' ,'');
    }

Does anyone have any ideas??? :pray:

The good old latency compensation? I grappled with this myself a while ago and created this little demo to learn about it. Check out if this is useful for you.