React library for building collaboration features with y-sweet.
npm install @y-sweet/react
Explore our collaborative examples to help you get started.
All examples are open source and live in this repository, within /examples.
Yjs models data as a nested data structure with types like Y.Array
corresponding to array data, Y.Map
corresponding to objects and key/value maps, and Y.Text
corresponding to strings. Each hook returns a Yjs type, so refer to Yjs documentation for details on how to use them.
In addition to returning a Yjs type, each hook also subscribes the component to changes to that type. By default, changes to that value, or any descendent of that value, triggers a rerender of the component.
The @y-sweet/react
hooks provide a way to access data for each of these types.
useMap
is a shared object or mapuseMap
exposes a Yjs Y.Map
. You can refer to the documentation for Y.Map
here.
Our Color Grid example uses useMap
to store a grid of colors using coordinates as keys.
const items = useMap<string>('colorgrid')
items.delete(key)
items.set(`${x},${y}`, color)
useArray
is a shared arrayuseArray
exposes a Yjs Y.Array
. You can refer to the documentation for Y.Array
here.
Our To Do List example, which uses useArray
to store a list of To Do items.
The To Do List stores an array of objects of type Y.Map (a Yjs type). This maintains the order of the To Do List, while being able to indicate whether an item is ‘done’.
const items = useArray<Y.Map<any>>('todolist')
const pushItem = useCallback((text: string) => {
let item = new Y.Map([
['text', text],
['done', false],
] as [string, any][])
items?.push([item])
},[items])
const clearCompleted = useCallback(() => {
let indexOffset = 0
items?.forEach((item, index) => {
if (item.get('done')) {
items.delete(index - indexOffset, 1)
indexOffset += 1
}
})
}, [items])
useText
is a shared stringuseText
exposes a Yjs Y.Text
. You can refer to the documentation for Y.Text
here.
Our Text Editor example, which uses useText
to store the text document.
const yText = useText('text')
useAwareness
returns an Yjs awareness objectThe awareness object can be applied to editor bindings with Yjs that have built in presence features.
Our Code Editor demo passes the awareness object to the CodeMirror Yjs binding.
const awareness = useAwareness()
bindingRef.current = new CodemirrorBinding(yText!, editorRef.current, awareness)
usePresence
for general purpose presence featuresThe usePresence
and usePresenceSetter
hooks are a higher-level React abstraction on top of Yjs’s awareness.
usePresence
returns a Map<number, object>
of presence data for other users. The key is the user ID, and the value is whatever that user has set as their presence data.
usePresenceSetter
returns a function that can be used to set the current user’s presence data.
Our Presence demo defines presence as a map of x,y keys and color value pairs.
'use client'
import { usePresence, usePresenceSetter } from '@y-sweet/react'
import { useCallback, useRef } from 'react'
const COLORS = ['bg-red-500', 'bg-blue-500', 'bg-green-500', 'bg-purple-500', 'bg-pink-500']
type Presence = { x: number; y: number; color: string }
export function Presence() {
const myColor = useRef(COLORS[Math.floor(Math.random() * COLORS.length)])
const presence = usePresence<Presence>()
const setPresence = usePresenceSetter<Presence>()
const updatePresence = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
setPresence({
x: e.clientX - e.currentTarget.offsetLeft,
y: e.clientY - e.currentTarget.offsetTop,
color: myColor.current,
})
},
[setPresence],
)
return (
<div
className="border-blue-400 border relative overflow-hidden w-[500px] h-[500px]"
onMouseMove={updatePresence}
>
{Array.from(presence.entries()).map(([key, value]) => (
<div
key={key}
className={`absolute rounded-full ${value.color}`}
style={{ left: value.x - 6, top: value.y - 8, width: 10, height: 10 }}
/>
))}
</div>
)
}