@ysdk/react
v0.1.2
Published
React hooks for collaborative apps. Multiplayer state that feels like useState.
Maintainers
Readme
@ysdk/react
React hooks for collaborative apps. Multiplayer state that feels like useState.
Built on Yjs. Works with any Yjs WebSocket server. No vendor lock-in.
Install
npm install @ysdk/reactQuick start
Option A: YSDK Cloud (zero setup)
import { YSDKProvider } from '@ysdk/react'
function App() {
return (
<YSDKProvider cloud={{ apiKey: "ysdk_live_...", room: "my-room" }}>
<MyApp />
</YSDKProvider>
)
}Option B: Self-hosted
npx y-websocketimport { YSDKProvider } from '@ysdk/react'
function App() {
return (
<YSDKProvider url="ws://localhost:1234" room="my-room">
<MyApp />
</YSDKProvider>
)
}3. Use shared state
import { useShared } from '@ysdk/react'
function Counter() {
const [count, setCount] = useShared('count', 0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}Open two browser tabs. Click the button. Both tabs update.
API
<YSDKProvider>
Connects to a Yjs sync server and provides shared state to all child components.
// YSDK Cloud (zero setup, hosted infrastructure)
<YSDKProvider cloud={{ apiKey: "ysdk_live_...", room: "my-room" }}>
// Self-hosted (any y-websocket server)
<YSDKProvider url="ws://localhost:1234" room="my-room">
// Bring your own Y.Doc (you handle sync)
<YSDKProvider doc={myYDoc}>
// Local only (no sync, useful for testing)
<YSDKProvider>| Prop | Type | Description |
|------|------|-------------|
| cloud | { apiKey, room, baseUrl? } | Connect to YSDK Cloud |
| url | string? | WebSocket server URL (self-hosted) |
| room | string? | Room name (used with url) |
| doc | Y.Doc? | Bring your own Yjs document |
useShared<T>(key, defaultValue)
Shared state. Works like useState but syncs across all clients.
const [name, setName] = useShared('name', 'Anonymous')Values are stored in a shared Y.Map. Objects are stored as whole values (last-write-wins on the entire object, not per-field). For deep collaborative object editing, use useYSDK() to access the raw Y.Doc.
useSharedArray<T>(key)
Shared array with CRDT merge semantics. Concurrent insertions merge cleanly.
const [items, ops] = useSharedArray('todos')
ops.push({ text: 'Buy milk', done: false })
ops.insert(0, { text: 'First item', done: false })
ops.delete(2)
ops.clear()| Operation | Description |
|-----------|-------------|
| ops.push(...items) | Append to end |
| ops.insert(index, ...items) | Insert at position |
| ops.delete(index, count?) | Remove items |
| ops.clear() | Remove all items |
| ops.length | Current item count |
useSharedText(key, defaultValue?)
Shared text with character-by-character CRDT merging.
const { value, setValue, ytext } = useSharedText('title', 'Untitled')
// Simple usage - like a controlled input
<input value={value} onChange={e => setValue(e.target.value)} />
// Advanced - bind ytext directly to CodeMirror, ProseMirror, Tiptap, etc.
// import { yCollab } from 'y-codemirror.next'
// extensions: [yCollab(ytext, awareness)]| Property | Type | Description |
|----------|------|-------------|
| value | string | Current text as plain string |
| ytext | Y.Text | Raw Yjs text for editor bindings |
| setValue | (text: string) => void | Replace entire text content |
usePresence<T>(initialState?)
Track who's connected and share ephemeral user state.
const { peers, setPresence, count } = usePresence({
name: 'Andy',
cursor: { x: 0, y: 0 },
})
// Update your cursor
onMouseMove={(e) => setPresence({ cursor: { x: e.clientX, y: e.clientY } })}
// Render other people's cursors
{peers.map((peer, i) => (
<Cursor key={i} x={peer.cursor.x} y={peer.cursor.y} name={peer.name} />
))}| Property | Type | Description |
|----------|------|-------------|
| peers | T[] | Other users' presence state |
| setPresence | (state: Partial<T>) => void | Update your presence (merges) |
| count | number | Number of connected peers |
useBroadcast<T>(channel)
Fire-and-forget messages to all connected clients. Not persisted.
const { broadcast, onMessage } = useBroadcast('reactions')
// Send
broadcast({ emoji: '🎉', x: 100, y: 200 })
// Receive
useEffect(() => {
return onMessage((data, senderId) => {
showReaction(data.emoji, data.x, data.y)
})
}, [])useYSDK()
Escape hatch to the raw Yjs document and awareness instance.
const { doc, awareness } = useYSDK()
// Full Yjs API access
const ymap = doc.getMap('my-custom-map')Self-hosting
Any Yjs WebSocket server works. The simplest:
npx y-websocketFor production, consider Hocuspocus which adds auth, persistence, and webhooks on top of Yjs.
Limitations (v0.1)
useSharedstores objects as opaque values (last-write-wins on whole object, not per-field CRDT merge). UseuseYSDK()for deep collaborative objects.useBroadcastuses awareness state internally. Not suitable for high-frequency events (>10/sec). Late joiners may see the last broadcast message.- Changing
urlorroomonYSDKProviderafter mount creates a new document (previous state is lost). - No built-in persistence. State exists only while at least one client is connected (unless your server persists).
License
MIT
