immer-yjs-typed
v0.0.0
Published
# STATUS: Alpha WIP - not ready for production use
Downloads
2
Readme
immer-yjs-typed
STATUS: Alpha WIP - not ready for production use
This library provides a simple way to support peer-to-peer and multi-user shared objects and text in React, Vue, etc. It combines Yjs, a CRDT library with a mutation-based API, with immer, a library with immutable data manipulation using plain JS objects. It is based on sep2/immer-yjs.
Installation
npm install immer-yjs-typed immer yjs
Documentation
import { bind } from 'immer-yjs'
.- Create a binder:
const binder = bind(doc.getMap("state"))
. - Add subscription to the snapshot:
binder.subscribe(listener)
.- Mutations in
y.js
data types will trigger snapshot subscriptions. - Calling
update(...)
(similar toproduce(...)
inimmer
) will update their correspondingy.js
types and also trigger snapshot subscriptions.
- Mutations in
- Call
binder.get()
to get the latest snapshot. - (Optionally) call
binder.unbind()
to release the observer.
Typing
The immer objects encode Yjs data into [type, value]
tuples.
Supported types are:
YMap
YArray
YText
XmlFragment
XmlElement
XmlText
object
array
string
number
boolean
null
For example, a Yjs object can be serialized into the equivalent JSON:
import { serialize } from 'immer-yjs-typed`
// Note that Yjs doesn't actually support this syntax
const ydata = new Y.Array([
new Y.Map({
title: 'write tests',
checked: true
}),
new Y.Map({
title: 'dogfood app',
checked: false
})
])
const json = serialize(ydata)
The same JSON can also be serialized back to a Yjs object:
import { deserialize } from 'immer-yjs-typed`
const json = ['YArray', [
['YMap', {
title: ['string', 'write tests'],
checked: ['boolean', true]
}],
['YMap', {
title: ['string', 'dogfood app'],
checked: ['boolean', false]
}]
]]
const ydata = deserialize(json)
React integration
For Zustand, see the recipes doc.
For vanilla React, the most efficent way is to use useSyncExternalStoreWithSelector:
import { bind } from 'immer-yjs-typed'
// Setup store
const doc = new Y.Doc()
const ymap = doc.getMap('appstate.v1')
// Attach immer-yjs-typed
const binder = bind(ymap);
// Initialize state
binder.update((state) => (
['YMap': {
count: ['number', 0]
}]
));
// define a helper hook
function useImmerYjs<Selection>(selector: (state: State) => Selection) {
const selection = useSyncExternalStoreWithSelector(
binder.subscribe,
binder.get,
binder.get,
selector
)
return [selection, binder.update]
}
// use in component
function Component() {
const [count, update] = useImmerYjs((s) => s[1].count[1])
const handleClick = () => {
update((s) => {
// any operation supported by immer
s[1].count[1]++
})
}
// will only rerender when 'count' changed
return <button onClick={handleClick}>{count}</button>
}
// when done
binder.unbind()