Y.js Binding
Overview
mobx-bonsai
includes a binding with Y.js
that creates a node that stays in sync with a Y.js
state in both directions.
For example, if you already have a Y.js
state representing:
interface Todo {
done: boolean
text: string
}
interface TodoAppState {
todoList: Todo[]
}
and that it is already prepopulated with some todos in a Y.js
doc map named "todoAppState". All you need to do is:
const {
mobxNode: todoAppState,
dispose
} = bindYjsToNode<TodoAppState>({
yjsDoc,
yjsObject: yjsDoc.getMap("todoAppState"),
})
and from then on you can read/write the state as if it were a MobX observable, this is:
// read
const doneTodos = todoAppState.todoList.filter(todo => todo.done)
// write
const toggleTodoDone = action((todo: Todo) => {
todo.done = !todo.done;
})
toggleTodoDone(todoAppState.todoList[0])
and it will be kept in sync with the Y.js
state.
Note that the sync is two-way, so if Y.js
state gets updated (manually or via a remote state update), the node will get updates as well. All that means that this:
yjsDoc.transact(() => {
const newTodo = new Y.Map()
newTodo.set("done", false)
newTodo.set("text", "buy milk")
yjsDoc.getMap("todoAppState").getArray("todoList").push([ newTodo ])
})
will also result in a new todo getting added to todoAppState.todoList
after the transaction is finished.
And of course, since this is a mobx-bonsai
enhanced MobX observable in the end, you can use reaction, autorun, when, computed, getParent, getSnapshot...
Working with Initial State
What if I don't have an intial Y.js
state yet?
You can create one like this:
applyPlainObjectToYMap(
yjsDoc.getMap("todoAppState"),
{
todoList: []
}
)
Binding Limitations
Y.js
binding limits:
- Changes in the MobX observable are replicated to
Y.js
only after the outermost MobX action has completed. Therefore, avoid executing anyY.js
transactions until MobX actions finish. Y.js
changes are merged into the MobX observable only after allY.js
transactions have concluded. Consequently, do not initiate MobX actions that modify the bound object during an ongoingY.js
transaction.
Utility Functions
getYjsObjectForNode
The getYjsObjectForNode
function returned by the bind function resolves a Y.js
structure corresponding to a given node within a bound tree. This is useful when you need to map a node back to its Y.js
counterpart.
If the target node is not part of the bound tree it throws an error.
import * as Y from "yjs"
import { bindYjsToNode } from "mobx-bonsai"
const { node, getYjsObjectForNode } = bindYjsToNode<TodoAppState>({
...
});
const firstTodoYjs = getYjsObjectForNode(todoAppState.todoList[0]);