In my last post, I went over how I created a contenteditable div in Ember. This lead me to want to see how I'd do it in React.
My First Implementation with a Jumping Cursor
I originally started with the following implementation, adhering to a controlled component data flow:
import { useState } from "react";
export default function App() {
const [content, setContent] = useState("foo");
return (
<div
contentEditable="true"
onInput={event => {
setContent(event.target.textContent);
}}
>
{content}
</div>
);
}The first problem is that there is the following warning in red in the console:
Warning: A component is
contentEditableand containschildrenmanaged by React. It is now your responsibility to guarantee that none of those nodes are unexpectedly modified or duplicated. This is probably not intentional.
The second problem is that as a user types into the contenteditable div, the cursor jumps to the beginning of the element.
The Solution
To fix this, I started by creating a component called Contenteditable that can be used as follows:
import { useState } from "react";
import Contenteditable from "./Contenteditable";
export default function App() {
const [content, setContent] = useState("foo");
return (
<>
<Contenteditable
value={content}
onChange={updatedContent => {
setContent(updatedContent);
}}
/>
<p>{content}</p>
</>
);
}Here is the implementation of the Contenteditable component:
import { useEffect, useRef } from "react";
export default function Contenteditable(props) {
const contentEditableRef = useRef(null);
useEffect(() => {
if (contentEditableRef.current.textContent !== props.value) {
contentEditableRef.current.textContent = props.value;
}
});
return (
<div
contentEditable="true"
ref={contentEditableRef}
onInput={event => {
props.onChange(event.target.textContent);
}}
/>
);
}Updating the textContent of the div causes the cursor to jump to the beginning, so to prevent this from happening on every keystroke, I conditionally assign the textContent only if it isn't the same as the value prop. The only time these two values might not be the same is on initial render where I specified an initial value of "foo" and the element's initial textContent is an empty string. Even if there isn't an initial value, and hence the values will be the same (an empty string), there won't be a problem as the cursor will just start at the beginning of the element. As a user types, these two values will be the same so the textContent won't get reassigned and thus the cursor won't jump to the beginning. Problem fixed!

