Closed joebobmiles closed 3 years ago
Probing the extents of this bug, seems that it only happens when inserting data that is translated to a shared type. For instance, this test, which inserts consecutive scalar values, passes:
type Store =
{
numbers: number[],
addNumber: (n: number) => void,
};
const doc = new Y.Doc();
const api =
create<Store>(yjs(
doc,
"hello",
(set) =>
({
"numbers": [],
"addNumber": (n) =>
set((state) =>
({
"numbers": [
...state.numbers,
n
],
})),
})
));
expect(api.getState().numbers).toEqual([]);
expect(() =>
{
api.getState().addNumber(0);
api.getState().addNumber(1);
}).not.toThrow();
Ok, so there seems to be two bugs, both only occur when performing consecutive inserts into Yjs Arrays. The first happens when inserting maps, which results in a TypeError: Cannot read property 'forEach' of null
. The second happens when inserting arrays, which results in a TypeError: Cannot read property 'length' of null
. I'm still unsure if these are bugs in Yjs, or something is wrong with the middleware.
I have found where the bug is located in the middleware. It happens when a nested shared type does not change, but is being re-inserted into the parent shared type. In essence, Yjs annotates all shared types with the data that is to be inserted/modified during a transaction. However, if a type is to be inserted, but has not changed since the last transaction, this annotation is null, resulting in the above type errors.
I feel like this is an oversight of Yjs, but a workaround would just be being smart about how we transact on YArrays when processing changes.
A proposed change would be, instead of wholly deleting the contents of the array and re-inserting, would be targetted deletion and insertion to only insert data that has changed.
Noticed in the RPG code that we're getting a TypeError from Yjs:
I've replicated the bug with the following test:
I'm unsure if this is a bug in Yjs or a bug in the middleware.