React hook for Yjs.
The hook automatically subscribes to changes in the Yjs data-structure and re-renders the component when the data changes. In addition it returns the result of the .toJSON
from the Yjs data-structure.
npm install react-yjs
import { useY } from "react-yjs";
export const MyComponent = ({ yArray }) => {
const names = useY(yArray)
return (
…
)
}
https://github.com/nikgraf/react-yjs/assets/223045/f5cbf5d7-381e-4d4b-8bce-95bbaeb8083f
import { useY } from 'react-yjs';
import * as Y from 'yjs';
const yDoc = new Y.Doc();
const yNames = doc.getArray<string>('names');
export const MyComponent = () => {
const names = useY(yNames);
return (
{names.map(name => <div>{name}</div>)}
)
}
const yDoc = new Y.Doc();
const yTodos = yDoc.getArray<Y.Map<string | boolean>>("todos");
// Any change of the todos (e.g. change checked) will trigger a re-render
const todos = useY(yTodos);
Change Todos:
// add a Todo
const todo = new Y.Map<string | boolean>();
todo.set("checked", false);
todo.set("text", newTodo);
yTodos.push([todo]);
// update the first Todo
yTodos.get(0).set("checked", true);
See the working example at https://react-yjs-example.vercel.app/. The code is available at examples/app/src/components/Todos.tsx.
const yDoc = new Y.Doc();
const yPosts = yDoc.getArray<Y.Map<string | Y.Array<string>>>("posts");
const yPost = new Y.Map<string | Y.Array<string>>();
yPosts.push([yPost]);
yPost.set("title", "Notes");
const yTags = new Y.Array<string>();
yTags.push(["cooking", "vegetables"]);
yPost.set("tags", yTags);
// Makes sure to listen only to changes of the tags of the first post
const yTagsOfFirstPost = yPosts.get(0).get("tags") as Y.Array<string>;
const tagsOfFirstPost = useY(yTagsOfFirstPost);
Remove a tag on the first post:
const tags = yPosts.get(0).get("tags") as Y.Array<string>;
tags.delete(index);
See the working example at https://react-yjs-example.vercel.app/. The code is available at examples/app/src/components/DeepStructure.tsx.
The useY
hook
The goals for this project are
useSyncExternalStore
to avoid tearingThis resulted in creating a single hook that does a observeDeep
the Yjs data-structure. This allows to expose one single hook to listen to deeply nested data-structures.
Still by passing in only a specific selector of a Yjs data-structure, the hook will only listen to that specific part of the data-structure.
Yjs doesn't provide the APIs to do this on the Doc level. It would be possible to work around that, but sticking to the Yjs philosophy felt like a better option.
The Yjs types could be much better https://github.com/yjs/yjs/pull/614. Once this is release we can improve the types.
Please contribute to the project financially - especially if your company relies on it. https://github.com/sponsors/nikgraf
The project is MIT licensed.