Search code examples
javascriptmeteorpublish-subscribemeteor-tracker

Tracker.autorun() not running on every update of a ReactiveVar


I have a Meteor ReactiveVar, which is used as an update trigger in a data store. However the tracker is not run every time the reactive var is set.

It seems that sometimes when the state is set in quick succession, the tracker doesn't run.

This is the code for my store:

const myStore = {
    states = {
        ...
    },

    updateTrigger = new ReactiveVar({ name: null, timeStamp: null }),

    setState({ name, value }) {
        this.states[name] = value;
        console.log('Set State', name);
        this.updateTrigger.set({ name, timeStamp: new Date().getTime() });
    },
};

And the tracker:

Tracker.autorun(() => {
    const updateTrigger = myStore.updateTrigger.get();
    console.log('Tracker', updateTrigger.name);
    if (updateTrigger.name === state-two) myFunction();
});

The console logs this:

'Set State state-one' // update state-one
'Tracker state-one'
'Set State state-two' // update state-two
'Set State state-one' // update state-one
'Tracker state-one'
'Set State state-three' // update state-three
'Tracker state-three'
'Set State state-one' // update state-one
'Set State state-two' // update state-two
'Set State state-three' // update state-three
'Tracker state-three'

I can't see why this is happening.

It seems like a race condition as it's indiscriminate as to which updates it logs and which ones it doesn't.

The states are updated quite frequently (state one once every 1.5s, and then the others every second or so).

Any suggestions as to what it going wrong, or as to other approaches welcome.

I could use a PubSub package. Generally I'm not a big fan of Tracker and ReactiveVar, but I'm not sure what's best practice here, and I don't want to use Tracker+ReactiveVar in some places, and PubSub in others.

Having each individual state as a ReactiveVar is not as option, as I need to persist the state to a database on update.


Solution

  • This is due to Tracker's Flush Cycle.

    From the tracker manual:

    ... by default, all changes to reactive values are batched up and put into effect all at once, when all JavaScript code has finished executing and the system is idle. ... This process ... is called flushing, and it's also possible to manually trigger it by calling Tracker.flush().

    Therefore, adding an explicit flush in your code will yield the desired effect:

    const myStore = {
        states = {
            ...
        },
    
        updateTrigger = new ReactiveVar({ name: null, timeStamp: null }),
    
        setState({ name, value }) {
            this.states[name] = value;
            console.log('Set State', name);
            this.updateTrigger.set({ name, timeStamp: new Date().getTime() });
            Tracker.flush(); // added this
        },
    };