Undo / redo on modified arrays leads to duplicated data

Hi there,

Is there an elegant way of handling undo / redo when someone else has modified the data you’re about to undo / redo?

Here’s a couple examples to show what I mean

Example 1: Text
Using https://tiptap.dev
Initial data: “”

Step 1 - User A: “” → "ABCDEF"
Step 2 - User B: “ABCDEF” → “ADDDDF”
Step 3 - User A UNDO: “ADDDDF” → "DDDD"

Now since this is text this is probably fine, but when you’re modifying arrays this can super easily put your data in an invalid state.

Example 2: multiple arrays
Initial data:
const arr1 = [1,2,3,4,5,6]
const arr2 = []
const arr3 = []

Step 1 - User A:
const arr1 = []
const arr2 = [1,2,3]
const arr3 = [4,5,6]

Step 2 - User B:
const arr1 = []
const arr2 = [1,2,6]
const arr3 = [4,5,3]

Step 3 - User A UNDO:
const arr1 = [1,2,3,4,5,6]
const arr2 = [6]
const arr3 = [3]

3 and 6 are now duplicated, which in my case breaks the data.
Has anyone run into a similar problem or know how one would go about solving this?

Thanks in advance!

Hi @philipaarseth,

What you really seem to want is a “move” feature in Yjs (that’s something that I’ve been working on). “moving of elements” will guarantee that there will be only one identity of each element. Moving an element twice shouldn’t result in duplication.

This issue has also been discussed in:

Now, this doesn’t solve your issue because the new move feature will not (yet) support moving between arrays. One simple solution, that I also suggested in the thread, is to have some kind of “cleanup” function that remove duplicate elements. It should consistently remove the same element (e.g. always the last element) to ensure that all clients end up with the same content without deleting the element entirely. A case you want to prevent is that user A cleans up the first duplicate, and user B cleans up the second duplicate.

Yeah that sounds exactly like what I need. A move function would actually solve a ton of issues, so very exciting that it’s already in the works!

Going to assume it’s this PR

Just to clarify, is it considered the re-parenting if you’re moving from one array to another inside the same ymap?
Which would be the not yet functionality, if so then yeah this wouldn’t really do much for us yet until you can move between arrays.

Hi, I posted about this issue in this thread.
In that case I was developing a simple collaborative Kanban board in which I can drag notes between different containers.
My solution was to identify duplications and avoid rendering them.
I identified duplications by giving IDs to everything, and having each note point to its container. Like this:

{
  "containers": {
    "1": {
      "name": "Doing",
      "noteIds": [
        "1",
        "2",
        "3" // Not rendered: note points to container "2"
      ]
    },
    "2": {
      "name": "Done",
      "noteIds": [
        "3",
        "3", // Not rendered: already appeared in this container
        "4"
      ]
    }
  },
  "notes": {
    "1": { "containerId": "1" },
    "2": { "containerId": "1" },
    "3": { "containerId": "2" },
    "4": { "containerId": "2" }
  }
}

Hope that helps.

1 Like

Just to clarify, is it considered the re-parenting if you’re moving from one array to another inside the same ymap?

Eventually, but not yet or in the near future. I recommend to go with @gustavotoyota approach (thanks for sharing!).