javascriptmeteormeteor-blazeflow-router

Inconsistent updating of Meteor template


Using Meteor with blaze templates and flow router, I find that if I create a new element then the page doesn't update to show it, but if I delete the same element it disappears immediately. Here's the template code:

<template name="EditProject">
    ...
    {{#each currentCounts }}
        <div class="count-box">{{> CountDelete }}</div>
    {{/each}}
    ...
    <btn class="btn waves-effect waves-light h-button" id="add-count">Add Count</btn>
    ...
</template>

<template name="CountDelete">
    <div class="card blue-grey lighten-2 count-box">
        <div class="card-content white-text">
            <span class="card-title">
                {{ name }}
            </span>
            {{ notes }}
        </div>
        <div class="card-action">
            <btn class="btn waves-effect waves-light delete-count">
                <i class="mdi mdi-delete"></i>
            </btn>
            <a class="btn waves-effect waves-light" href="/edit_count/{{ _id }}">
                <i class="mdi mdi-pencil"></i>
            </a>
        </div>
    </div>
</template>

The source of currentCounts is this:

Template.EditProject.helpers({
    currentCounts: function() {
        var projectId = FlowRouter.getParam('projectId');
        const project = Projects.findOne(projectId);
        var counts = Counts.find({
            _id: { $in: project.counts }
            },
            {
                sort: { sort_order: -1 }
            }
        );
        return counts;
    }
})

As mentioned, clicking on a .delete-count button deletes the associated count and also causes the UI to update to show that it has gone. Adding a count (from a click on #add-count) creates the count correctly but the page does not update. There's a brief flicker, but that's all, and refreshing the page causes the new count to show up. Could anyone suggest what's going on?

Edit: Here's the subscription, as requested in a comment:

Template.EditProject.onCreated(function() {
    var self = this;
    self.autorun(function() {
        var projectId = FlowRouter.getParam('projectId');
        self.subscribe('single-project',projectId);
        self.subscribe('project-counts',projectId);
    })
})

Further edit:

When first accessing this route the page renders as it should, showing a list of several counts via the {{#each currentCounts}}. If I delete one of those counts it instantly disappears from the screen but if I add a new one it doesn't show up until I refresh the page.

Another edit:

Listeners and server publication code (in server/main.js) added as requested. Oddly, when starting the application again everything started behaving as it should, but within a few minutes the same behaviour I've been describing reasserted itself.

Meteor.publish('project-counts', function projectPublication(projectId) {
    let project = Projects.findOne({_id: projectId, knitter: this.userId});
    return Counts.find({_id: { $in: project.counts }});
});
Meteor.publish('single-project', function projectPublication(projectId) {
    return Projects.find({_id: projectId, knitter: this.userId});
});

'click #add-count'(event) {
    //TODO: Check for duplicate count name
    var projectId = FlowRouter.getParam('projectId');
    var countName = $('#new-count').val();
    var countNotes = $('#new-count-notes').val();

    if (!countName) {
        $("#errors-go-here").empty();
        Blaze.renderWithData(Template.EditProjectErrors, {
            errors: ['You must supply a name for the new count.']
        }, $("#errors-go-here")[0])
        $('.modal').modal();
        $('#validation-errors').modal('open');
        return;
    }

Template.EditProject.events({
    ...
    Meteor.call('projects.add-count',projectId,countName,countNotes, function(error) {
            if (error) {
                console.log("Count add error: " + error);
                Materialize.toast('Failed to add count \'' + countName + '\'!', 3000, 'orange darken-4');
                return false;
            } else {
                Materialize.toast('Count \'' + countName + '\' added!', 3000, 'blue-grey');
                $('#new-count').val(null);
                $('#new-count-notes').val(null);
                // Running this makes the missing count show up, but this is clearly not the right way to do it...
                //location.reload(); 
            }
        });
    },
    ...
)}



Template.CountDelete.events({
    'click .delete-count'(event) {
        var self = this;
        var projectId = FlowRouter.getParam('projectId');
        var countId = self._id;
        const count = Counts.findOne(countId);
        const name = count.name;

        Meteor.call('projects.delete-count',projectId,countId, function(error) {
            if (error) {
                console.log("Count add error: " + error);
                Materialize.toast('Failed to delete count \'' + name + '\'!', 3000, 'orange darken-4');
                return false;
            } else {
                Materialize.toast('Count \'' + count.name + '\' deleted!', 3000, 'blue-grey');
            }
        });

    },

})

Further information: I found that once the page is loaded it behaves as it should. However, if it is reloaded then it starts misbehaving. So, I'd originally not noticed the correct behaviour happening as the page had been refreshed by Meteor due to changes to the code.


Solution

  • To be complete, you should probably have also shared the methods code (i.e. 'projects.add-count' and 'projects.delete-count').

    That being said, I suspect they update the counts array field of document with _id equal to projectId in Projects collection.

    In that case, looking at your 'project-counts' publication, we see that it depends on a Projects.findOne query, which is not reactive on server-side with standard Meteor.

    Therefore what happens when you add a "count" is that a new document is added into Counts collection, and its _id is very probably correctly recorded in the project's counts field, but this does not re-execute your publication code. Therefore the cursor selector query is unchanged, and your client does not receive any modification in its Counts collection.

    Assuming your method code is loaded in both client and server, the client performs a stub / simulation that inserts this new document and locally updates the project's counts, triggering a UI update.

    But then your server executes the method as well, which leads to no change in the publication, therefore the new document in Counts is not sent to the client, which hides it back. This creates the flicker that you observe.

    Now when you delete a document from Counts, we can assume that in particular you remove it from Counts collection, which then updates the publication cursor, that is why your UI correctly reflects the deletion, even though the publication query selector was not changed.

    Finally, when you refresh the page, your publication code is entirely re-evaluated, using the latest version of your document in Projects collection, which leads to a correct query selector, hence your new cursor now includes the newly added document in Counts.

    Then to solve your issue, you should try to use reactive joins or publish composite packages.

    E.g. see https://stackoverflow.com/a/32920733/5108796 (Meteor.publish: publish collection which depends on other collection)