Collaborative text field using react with debounce
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 6.5k
- Forks
- 456
- Avg merge
- 1d 5h
- Merged PRs (30d)
- 5
Description
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 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.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the existing /examples/textarea implementation and compare it with the React example shown in the issue. Investigate the requested debounce behavior and how the history collection is handled, then define whether the result should be a framework example, a package change, or guidance; done requires an agreed scope and documented behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, react
- Domain
- distributed-systems, frontend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100