I can't know which YData I deleted through the event.changes.deleted property

const Y = require('yjs');
const doc = new Y.Doc();
const yArr = doc.getArray('arr');
yArr.observe((event, trans) => {
    event.changes.added.forEach(item => {
       item.content.getContent().forEach(yData => {
          console.log('added obj', yData.toJSON()); // { id: 10000, name: 'Alex', 'age': 27}
       });
    });
    event.changes.deleted.forEach(item => {
        item.content.getContent().forEach(yData => {
            console.log('deleted obj', yData.toJSON()); // {}
        });
    });
});
const yItem = new Y.Map();
yItem.set('id', '10000');
yItem.set('name', 'Alex');
yItem.set('age', 27);
yArr.push([yItem]);
yArr.delete(0, 1);

When I delete an element from Y.array, how does the peer know which one I deleted, because from the event.changes.deleted property, the deleted element printed is an empty object. Who can help me?

I’m facing a similar problem here too.
When deleting an object from an array, event.changes.delta gives only the index of the deleted object, and I can’t even use this index to directly find out which object was deleted.
The workaround I’m using right now is to keep another array that mirrors my Y.Array, so I can use the given index to find the deleted object.

myYArray.observe(event => {
  let index = 0

  for (const delta of event.changes.delta) {
    if (delta.retain != null)
      index += delta.retain

    if (delta.delete != null) {
      const deletedObjs = myMirrorArray.splice(index, delta.delete)
      console.log('Deleted objects:', deletedObjs)
    }

    if (delta.insert != null)
      myMirrorArray.splice(index, 0, ...delta.insert)
  }
})

This is only a half-baked solution, but it might be interesting to see if it could become a general solution with some input from others.

Since Yjs stores the entire history of changes, it also contains info about the deleted item. Could we try to iterate on that history and get the item out that was deleted?

Here is my partial attempt:

export function getDeletedItems(event, transaction) {
  const items = [];

  Y.iterateDeletedStructs(
    transaction,
    transaction.deleteSet,
    /** @param {Item|GC} item */ (item) => {
      if (
        item instanceof Y.Item &&
        item.deleted &&
        pathLength(event.target, item) == event.path[0]
      ) {
        items.push(item);
      }
    }
  );

  return items;
}

export function pathLength(ancestor, child: Y.Item) {
  let length = 1;
  while (child !== null) {
    if (child.parent === ancestor) {
      return length;
    }
    child = (child.parent as any)._item;
    length++;
  }
  return null;
}

The above would be called inside an observe function. I’m not sure if it’s possible to re-construct the entire item, but I’ve been able to get the bits and pieces I need out of it this way.

1 Like

Thank you @canadaduane, that’s a solid approach (although probably not very performant for large data structures).

Usually, users want to “bind” a Y.Array to an external data object (e.g. the react-state of the list of todos). It is usually not necessary to know what exactly has been deleted because the data is still available elsewhere. The event.changes API is optimized for data binding. You can probably imagine that resurfacing the content of the deleted items comes with a cost (lookup, no garbage collection, memory consumption of the events object, additional complexity of the diff calculation, etc…).

This is why there is no information about the deleted objects available. I hope that you can work around it. Aside from @canadaduane’s approach I have no good alternative solution at the moment…

@zhu toJSON doesn’t work because the item is deleted.