Reactivity problem when adding an element to an array in a document

My setup:

// an item that can have many children items
{
    _id: "item0",
    text: "blabla"
    // array of children to render inside the current one
    children: ["item1", "item2", "item3"]
}

// simplification of the helpers I use to render the children
Template.item.helpers({
    // gets the children array from the item document in mongo
    getChildren: function () {
        return  ItemsList.findOne(this._id, {children:1, _id: 0}).children;
    }
});

If I add a new child (_id:“item4”) the the chidren array in position 1 using a method that calls:

Items.update(itemId, {$push:{children: {$each: ["item4"], $position: 1}}});

so “item0” ends up like this:

{
    _id: "item0",
    text: "blabla"
    children: ["item1", "item4", "item2", "item3"]
}

The problem:

Meteor calls getChildren() helper twice, once with the children array like this:

// notice "item4" at the end of the array
["item1", "item2", "item3", "item4"]

and then like this:

// notice "item4" at the correct position
["item1", "item4", "item2", "item3"]

DDP is sending the correct information with the array in the correct order and never sends anything in the wrong order.

This weird behavior is creating a very ugly flicker in the app.

Please help!

I see this behaviour if I perform the update on the client (I used a click event to execute the insert).

If I put the insert on the server, in a Meteor.method, and use the click event to do a Meteor.call it works as expected.

This leads me to believe that you are seeing a mismatch in functionality between minimongo and Mongo. When you insert on the client, minimongo adds the element to the end of the array and you see that. Then Mongo on the server performs the expected action and send the result down via DDP where the optimistic UI action is corrected - hence the flicker.

@robfallows Thanks for the answer.

I’m using a method to do the change. It’s a keyboard event that calls a method like this simplified version:

changeItemParent: function(childItemId, parentItemId, positionIndex) {
    // erases the child item from any item that may have it as a child
    ItemsList.update({children: childItemId}, {$pull:{children: childItemId}}, {multi: true} );
    // adds item to the new parent in the correct position
    ItemsList.update(parentItemId, {$push:{children: {$each: [childItemId], $position: positionIndex}}});
}

Minimongo should send the same data that the server sends right? I don’t understand why would it put it at the end of the array first, render the element and then put it in the correct position. Is there a way to tap into what minimongo does o to see how the reactivity is happening? I’m out of solutions! Thanks!

I’ve run the tests again and I still don’t see the mis-render with a server method. However, that could just be because I’m running Meteor in a local VM and the client/server latency is practically zero.

You could try using a rawCollection in the method:

ItemsList.rawCollection().update(
  {_id: itemId},
  {$push:{children: {$each: ['item4'], $position: 1}}},
  function(err, result) {
    if (err) {
      throw new Meteor.Error('mongo', 'got an error');
    }
  });

I see the same result in my client with this code, which bypasses minimongo for the server-side code, so may be worth trying :smile:.

Just to (hopefully) put this to rest. It looks like the $position argument has not been supported in minimongo. However, it’s coming in the next release.

@robfallows Genius man. I think that may be it, the $position support in minimongo. I created an issue in meteor’s github that has the code for the simplest program I could create to reproduce the issue. Here it is:

https://github.com/meteor/meteor/issues/4624

You can clearly see the flicker there. rawCollection() seems to only work on the server so calling the update only in the server (no minimongo) solves the flicker. No more latency compensation though, which I really need.

I’ll wait for the meteor update and see if that fixes the problem! I’m also posting this info in the ticket in github.

Thanks a lot for the help rob!