react-trumbowyg
react-trumbowyg copied to clipboard
Updates and the Cursor
I'm using this to build a collaborative editor. I am saving the document on every onChange
that fires. This is all working well, when user #1 makes a change, it shows up on user #2's screen instantly (and vice versa). It's working great albeit one problem: whenever the component updates with new text, the cursor is sent back to the front of the input:
http://g.recordit.co/j9VeyvM0ka.gif
Any idea how to avoid this but still have the text/contents update? I'll post an example repo shortly...
So, this is only happening when html is being injected as the value, but works fine when the value={} is receiving just a string:
http://g.recordit.co/HRoovYVz1K.gif
Here's an example repo: https://github.com/acomito/collab-editor
I have had the same problem. I decided to remove onChange for every change and fire it only when user press a Save button. But it can be applied to your situation as I understand. So, we need to find a way to move cursor inside a text box after every onChange. I recommend you to look into trymbowyg component. In mean time, I'll explore possibilities to move cursor via jQuery.
Thanks @sochix. If it's helpful, I found it the same example app worked fine with react-medium-editor even with html as the value. I haven't had a chance to dig in and compare react-medium-editor
and react-trumbowyg
to see how they differ in handling updates.
Yop ! I'm the Trumbowyg's creator. I think it's due to Trumbowyg core that didn't restore cursor position after updating the HTML content.
The problem with cursor is deep inside trymbowyg core. So need time to investogate further
Okay thanks for checking it out @sochix
Hey, @sochix @Alex-D Any updates on this issue?
Nope. I don't have the time to work on Trumbowyg for now.
@bhattjay I explored it a bit. The problem is deep inside the core of Trumbowyg.
So on edit I populate the data props from incoming container component props and init my internal state value.
On change I update the internal state value with the rich text (html) value - which does nothing to the cursor position since the data value is originally coming from props and state updates have no impact.
When user hits save I take the value from state, since that is always up to date due to being sync'd with the onChanged event, and use that to update the DB.
I am not sure if this is what @acomito meant but it works pretty well so far.
Any update on this?
Hey @acomito what did you end up doing. This is the best editor I found but this bug makes it unusable. I am looking for rich text editing plus View Source feature
@Alex-D & @sochix If you can give me some hints on where to look for this cursor issue, I would be happy to look into it
I managed an ugly workaround for this (which I'm hiding in this wrapper): Tl;Dr => Use state to only trigger the editor to change if you modify the value outside the editor.
export interface WsyiwigTextAreaProps {
value: string;
onChange(newValue: string): void;
}
export interface WsyiwigTextAreaState {
value: string;
}
export class WsyiwigTextArea extends React.Component<WsyiwigTextAreaProps, WsyiwigTextAreaState> {
public constructor(props: WsyiwigTextAreaProps) {
super(props);
this.state = {
value: props.value
};
}
public componentWillReceiveProps(props: WsyiwigTextAreaProps) {
// We only want to update state if the external props are different from what we have in the editor. Calling the onchange method inside the editor component causes the cursor to be reset
if (this.props.value != props.value && props.value != $("#react-trumbowyg")[0].innerHTML) {
this.setState({
value: props.value
});
}
}
private handleChange = (e: any): void => {
this.props.onChange(e.target.innerHTML);
}
public render() {
let buttons = ['formatting', 'strong', 'em', 'del', 'unorderedList', 'orderedList', 'removeformat'];
return (
<>
<Trumbowyg id='react-trumbowyg' data={this.state.value} onChange={this.handleChange} buttons={buttons} />
</>
);
}
}
@tmoody460
I think this is a good workaround and it should be implemented inside react-trumbowyg
somehow.
I also had this issue and this was my solution:
const Comment: React.FC<Props> = () => {
const [newComment, handleNewComment] = useState();
const handleCommentChange = (e: SyntheticEvent<HTMLBaseElement>): void => {
const nodeList = e.currentTarget.childNodes;
handleNewComment(
Array.from(nodeList).reduce((content: string, node: any) => {
if (node.nodeType === 1) {
return content + node.outerHTML;
}
if (node.nodeType === 3) {
return `${content}<p>${node.textContent}</p>`;
}
return content;
}, "")
);
};
return (
<Trumbowyg
autogrow
data={!newComment ? "" : undefined} // <- This is the important part
buttons={["btnGrp-semantic", ["undo", "redo"], ["link"]]}
placeholder={staticFormatMessage(messages.commentPlaceholder)}
name="comment"
onChange={handleCommentChange}
onPaste={handleCommentChange}
onFocus={() => setActive(true)}
/>
)
}
The important part is in the data-prop
@andreas0607 I don't understand what we achieve by assigning data
prop's value ""
or undefined
. You can achieve the same thing by just passing data={""}
. Then extract the value inside Trumbowyg textarea using state (event.target.innerText). Please see live Example.
Cheers.
@evg1n I don't remember exactly why I used this solution. In hindsight I should've written a reason for doing it that way, BUT what I imagine was the reason was probably mutations of the parent component causing rerenders of trumbowyg. When I check if the newComment exists I am disabling the data-prop by setting it to undefined, which in turn stops trumbowyg from reading that the data has updated (which it hasn't, but is implied by a rerender). When I want to reset the field I just use my handleNewComment and set the value to undefined which resets the data-field. In your live example, this wouldn't be an issue. Because you don't have parents causing rerendering of children. Obviously, my solution is hacky. But if you get the same problem I had, then it is one possible solution.