You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I wanted to create a collaborative text field. The closest one I could find was the textarea example located in /examples/textarea and is using a third party package called sharedb-string-binding. However it is using plain html with javascript, so by looking at the code I tried to recreate it using react (but could be done using angular/vue/svelte).
import { Stack, TextField } from "@mui/material";
import ReconnectingWebSocket from "reconnecting-websocket";
import { useMount, useUnmount } from "react-use";
import { useEffect, useState } from "react";
import { Connection } from "sharedb/lib/client";
import { cloneDeep } from "lodash";
import jsondiff from "json0-ot-diff";
import diffMatchPatch from "diff-match-patch";
const App = () => {
const [socket, setSocket] = useState(null);
const [doc, setDoc] = useState(null);
const [docData, setDocData] = useState(null);
useMount(() => {
const socket = new ReconnectingWebSocket(
process.env.REACT_APP_SOCKER_URL,
[],
{
maxRetries: 3,
}
); // connect to the socket
setSocket(socket); // save the socket instance
const connection = new Connection(socket); // create a connection
const document = connection.get("test-collection", "123"); // get the document
setDoc(document); // save the document to state
document.subscribe((e) => {
// subscribe to the document
if (e) {
console.error(e);
return;
}
// If document.type is undefined, the document has not been created, so let's create it
if (!document.type) {
document.create(
{
collaborativeField: "",
},
(e) => {
if (e) {
console.log(e);
}
}
);
}
setDocData(cloneDeep(document.data)); // save document data to state, cloneDeep is mandatory
});
});
useUnmount(() => {
socket?.close();
doc?.unsubscribe();
setDocData(null);
});
useEffect(() => {
if (!doc || !docData) return;
// setup the onOp listener.
const onOp = (_e, source) => {
if (source) return; // if source is true, meaning its acknowledging user's own operations, do nothing
setDocData(cloneDeep(doc.data)); // else save the updated document data to state
};
doc.on("op", onOp);
return () => doc.removeListener("op", onOp);
}, [doc, docData]);
const handleInputChange = (e) => {
let newValue = e.target.value; // the new value
const newDocData = cloneDeep(docData); // create a copy of the document data in order to modify it locally in the function
newDocData.collaborativeField = newValue; // update the field with the new value
const diff = jsondiff(docData, newDocData, diffMatchPatch);
// use the jsondiff package to generate the json0 operations ...
// to go from the docData to newDocData...
// use diffMatchPatch as third argument because our field is a string
console.log(diff);
setDocData(newDocData); // set the new docData to state because we do not acknowledging our own operations
doc.submitOp(diff); // submit the operation
};
return (
<Stack
sx={{ height: "100vh", alignItems: "center", justifyContent: "center" }}
>
{docData && ( // render if docData is initialized
<TextField
label="Collaborative text field"
variant="outlined"
size="small"
value={docData.collaborativeField}
onChange={handleInputChange}
/>
)}
</Stack>
);
};
export default App;
feel free to comment at any part of my implementation as I would like to make a PR to provide an example using any of the modern front-end frameworks. However the main issue is if it is possible to add a debounce in order to significantly reduce the number of operations that are submitted. Both in my implementation and in the provided textarea example, a new operation is submitted on every keypress meaning that a new document is created in the history collection. Imagine we have an application with many collaborators and many collaborative fields. The history will become enormous quite fast. A debouncer will significantly reduce the size of the history collection but even with a debouncer in place is there a recommended way of handling the history?
Thanks.
The text was updated successfully, but these errors were encountered:
I have to say we used to debounce, but we actually stopped because it's a much smoother collaboration experience without the debounce (since edits arrive at remote users' editors as fast as possible).
If you don't need to go back to previous documents, you can TTL your ops collection.
First of all thanks for this awesome package!
I wanted to create a collaborative text field. The closest one I could find was the textarea example located in
/examples/textarea
and is using a third party package calledsharedb-string-binding
. However it is using plain html with javascript, so by looking at the code I tried to recreate it using react (but could be done using angular/vue/svelte).feel free to comment at any part of my implementation as I would like to make a PR to provide an example using any of the modern front-end frameworks. However the main issue is if it is possible to add a debounce in order to significantly reduce the number of operations that are submitted. Both in my implementation and in the provided textarea example, a new operation is submitted on every keypress meaning that a new document is created in the history collection. Imagine we have an application with many collaborators and many collaborative fields. The history will become enormous quite fast. A debouncer will significantly reduce the size of the history collection but even with a debouncer in place is there a recommended way of handling the history?
Thanks.
The text was updated successfully, but these errors were encountered: