draft-js
draft-js copied to clipboard
How to stop DraftJS cursor jumping to beginning of text?
Code involved using DraftJS and Meteor Js application Task - Make a live preview where text from DraftJS will get saved to DB and from DB it get displayed on another component.
But problem is once data comes from DB and I try to edit DraftJS cursor moved to the beginning.
Code is
import {Editor, EditorState, ContentState} from 'draft-js';
import React, { Component } from 'react';
import { TestDB } from '../api/yaml-component.js';
import { createContainer } from 'meteor/react-meteor-data';
import PropTypes from 'prop-types';
class EditorComponent extends Component {
constructor(props) {
super(props);
this.state = {
editorState : EditorState.createEmpty(),
};
}
componentWillReceiveProps(nextProps) {
console.log('Receiving Props');
if (!nextProps) return;
console.log(nextProps);
let j = nextProps.testDB[0];
let c = ContentState.createFromText(j.text);
this.setState({
editorState: EditorState.createWithContent(c),
})
}
insertToDB(finalComponentStructure) {
if (!finalComponentStructure) return;
finalComponentStructure.author = 'Sandeep3005';
Meteor.call('testDB.insert', finalComponentStructure);
}
_handleChange(editorState) {
console.log('Inside handle change');
let contentState = editorState.getCurrentContent();
this.insertToDB({text: contentState.getPlainText()});
this.setState({editorState});
}
render() {
return (
<div>
<Editor
placeholder="Insert YAML Here"
editorState={this.state.editorState}
onChange={this._handleChange.bind(this)}
/>
</div>
);
}
}
EditorComponent.propTypes = {
staff: PropTypes.array.isRequired,
};
export default createContainer(() => {
return {
staff: Staff.find({}).fetch(),
};
}, EditorComponent);
Any helpful comment in right direction will be useful
Also find out that issue occurs if we right below code in handleChange method
let { editorState : olderState} = this.state; if(olderState == editorState ) return; this.setState({editorState
Which versions of Draft.js, and which browser / OS are affected by this issue? Did this work in previous versions of Draft.js?
Using DraftJs v 0.10
I have the same problem. I load a raw editor state into a draft js Editor component, then click the mouse anywhere in the Editor. I see a blinking cursor at the point where I clicked, but when I start typing the cursor always jumps to the beginning of the content in the Editor. Very frustrating. In a long document users are definitely going to want to use the mouse to quickly move to places they want to edit. Being stuck using only the keyboard and always from the beginning of the content is not an acceptable workaround.
@SevenZark @Sandeep3005 view my solution: https://github.com/facebook/draft-js/issues/989#issuecomment-332522174
Anyone knows how to solve this? THe cursor jumps before last letter, when beginning to enter text
Still unsure exactly why this happened, or why my fix worked, but I managed to get around this with a few steps.
- Storing the text of the editor in the state of my component
- Checking that text has changed in
componentWillReceivePropsbefore creating the new editor - Pass the old SelectionState to the new editor in
componentWIllReceiveProps - Remembering to update the state with the new text whenever editor changes
import { CompositeDecorator, ContentState,
Editor, EditorState, Modifier } from 'draft-js';
const decorator = new CompositeDecorator([
... my decorator
]);
class MyEditor extends Component {
constructor(props) {
super(props);
const contentState = ContentState.createFromText(props.text);
const editorState = EditorState.createWithContent(contentState, decorator);
this.state = {
...props,
editorState,
};
this._onEditorChange = this._onEditorChange.bind(this);
}
_onEditorChange(editorState) {
const content = editorState.getCurrentContent();
const newText = content.getPlainText();
this.setState({
editorState,
text: newText // update state with new text whenever editor changes
}, () => this.props.onChange(newText));
}
componentWillReceiveProps(nextProps) {
const { editorState, text} = this.state;
const nextText = nextProps.text;
if (text !== nextText) { // check that text has changed before updating the editor
const selectionState = editorState.getSelection();
const newContentState = ContentState.createFromText(nextProps.text);
const newEditorState = EditorState.create({
currentContent: newContentState,
selection: selectionState, // make sure the new editor has the old editor's selection state
decorator: decorator
});
this.setState({
...nextProps,
editorState: newEditorState
});
}
}
render() {
const { editorState } = this.state;
return (
<div
style={{
border: '1px solid #ccc',
cursor: 'text',
minHeight: 80,
padding: 10
}}>
<Editor
editorState={editorState}
onChange={this._onEditorChange}
placeholder="Enter text" />
</div>
);
}
}
export default MyEditor;
Has anyone else recently encountered this? I am having the same issue after hydrating the editorState from an API call. After focusing the content, and entering a character, the cursor jumps to the start of the content.
None of the fixes I have seen across several threads seem to do the trick.
same issue
In case this proves helpful to someone in the future. I recently observed the behavior described here (cursor jumping to the beginning of the editor content) when unexpected entity data made its way into the editor state due to a copy-paste from another rich-text source.
For me, the solution was to always update the editorState in the local state. Something along these lines:
handleChange = editorState => {
if (/* some condition */) {
// Do whatever you need to
}
// Aways update it since handleChange is always called when
// the selection changes or when the content changes
this.setState({ editorState });
}
...
<Editor onChange={this.handleChange} {...otherProps} />
const newState = EditorState.createEmpty()
this.setState({
editorState: EditorState.moveFocusToEnd(newState)
})
This worked for me...
Here's how I ended up solving my problem.
Note I am serializing both content state and selection state so I can restore the entire editor state on subsequent returns. I am also using a decorator that is applying styling to words. This is also in a functional component rather than a class based one -- I say that because I have had different issues using functionless as opposed to class based.
// inside component
const editor = useRef(null);
const [editorSelection, setEditorSelection] = useState(
new SelectionState(selection)
);
let [editorState, setEditorState] = useState(
EditorState.set(EditorState.createWithContent(convertFromRaw(content)), {
decorator: decorator
})
);
const focus = () => editor.current.focus();
useEffect(() => {
focus();
setEditorState(EditorState.forceSelection(editorState, editorSelection));
/* ... */
}, []);
I have tried it a hundred different ways to create the editor state in one go with content state, selection state and the decorator or applying one after another in different orders (forceSelection, EditorState.set, EditorState.create). E.g.:
EditorState.create({
contentState: //
selection: //
decorator: //
})
These didn't work as I'd end up losing either the decorator or the selection. Only this seemed to work. You have to call focus before you set your selection.
const newState = EditorState.createEmpty() this.setState({ editorState: EditorState.moveFocusToEnd(newState) })This worked for me...
but if you have another input ,it cannot focus into other input box
Did a bit of debugging. Seems like the editor internally erroneously updates the block key after typing in the first character, but keeps it constant for subsequent updates.
const handleEditorChange = newEditorState => {
const rawData = convertToRaw(newEditorState.getCurrentContent());
const currentContentTextLength = editorState.getCurrentContent().getPlainText().length;
const newContentTextLength = newEditorState.getCurrentContent().getPlainText().length;
if (currentContentTextLength === 0 && newContentTextLength === 1) {
// WORKAROUND: listens to input changes and focuses/moves cursor to back after typing in first character
setEditorState(EditorState.moveFocusToEnd(newEditorState));
} else {
setEditorState(newEditorState);
}
}
This is a workaround that worked for me - hope it helps others.
const newState = EditorState.createEmpty() this.setState({ editorState: EditorState.moveFocusToEnd(newState) })This worked for me...
but if you have another input ,it cannot focus into other input box
In that case, use moveSelectionToEnd.
/**
- Move selection to the end of the editor without forcing focus. */ static moveSelectionToEnd(editorState: EditorState): EditorState;
The root cause of my issue appears to have stemmed from how I was resetting the DraftJs content. I'm using DraftJS for a chat application. The first chat message was fine and didn't exhibit the cursor jumping to the beginning but there was a high probability that any subsequent messages would have the cursor jump.
Before, I would reset the DraftJS content with the following:
const newEditorState = EditorState.createEmpty();
draftJsField = EditorState.moveFocusToEnd(newEditorState);
What fixed it for me was moving to the following:
draftJsField = EditorState.moveFocusToEnd(EditorState.push(editorState, ContentState.createFromText(''), 'remove-range'));
I found through some searching that if you need to clear the DraftJs content, it is recommended to perform a 'createFromText' rather than a 'createEmpty'. The 'createEmpty' should only be used on initialization.
So my initial chat message uses the 'createEmpty' but all subsequent messages use the 'createFromText' to clear the content and reset the focus. Hope this helps.
const newState = EditorState.createEmpty() this.setState({ editorState: EditorState.moveFocusToEnd(newState) })This worked for me...
This case just work in user edit in the end, if user edit in text of middle, cursor turn to the end, it's not the right way...
const newState = EditorState.createEmpty() this.setState({ editorState: EditorState.moveFocusToEnd(newState) })This worked for me...
This case just work in user edit in the end, if user edit in text of middle, cursor turn to the end, it's not the right way...
I too faced the same issue. so do we have a solution where content can be edited in the middle also
I was facing the same issue, and none of the above options worked out for me, so this is what I did:
function fixCursorBug(prevEditorState, nextEditorState) {
const prevSelection = prevEditorState.getSelection();
const nextSelection = nextEditorState.getSelection();
if (
prevSelection.getAnchorKey() === nextSelection.getAnchorKey()
&& prevSelection.getAnchorOffset() === 0
&& nextSelection.getAnchorOffset() === 1
&& prevSelection.getFocusKey() === nextSelection.getFocusKey()
&& prevSelection.getFocusOffset() === 0
&& nextSelection.getFocusOffset() === 1
&& prevSelection.getHasFocus() === false
&& nextSelection.getHasFocus() === false
) {
const fixedSelection = nextSelection.merge({ hasFocus: true });
return EditorState.forceSelection(nextEditorState, fixedSelection);
}
return nextEditorState;
}
This was based on how the SelectionState changed in my experiments, described as follows:
onChange(nextEditorState) {
console.info(nextEditorState.getSelection().serialize());
this.props.onChange(nextEditorState);
}
Looking at those console logs, I saw lines like:
Anchor: a05u4:0, Focus: a05u4:0, Is Backward: false, Has Focus: false // when the editor is empty
Anchor: a05u4:1, Focus: a05u4:1, Is Backward: false, Has Focus: false // when I type the first character
Anchor: a05u4:0, Focus: a05u4:0, Is Backward: false, Has Focus: true // when the cursor is reset to position 0
Decided to specifically target this case.
fixCursorBug
@kaustubh-karkare , where did you apply the function "fixCursorBug" at?
I used it in my onChange method given as a prop to the <TextEditor> component:
onChange(nextEditorState) {
nextEditorState = fixCursorBug(this.props.editorState, nextEditorState);
this.props.onChange(nextEditorState);
}
or
onChange(nextEditorState) {
nextEditorState = fixCursorBug(this.state.editorState, nextEditorState);
this.setState({editorState: nextEditorState});
}
I had the same problem, it works fine just using useState, but once I tried to save my editor into redux and then load, it would always put the cursor to the left.
I have fixed this by using both useState and a redux update. I update useState first and then dispatch an action to update redux and it keeps the cursor position.
Here's a watered down version of my component.
const RichTextEditor = (props) => {
const { widget, updateWidget } = props;
const { rawState } = widget.menu;
const editor = useRef(null);
// Create new empty state or existing state
const [editorState, setEditorState] = useState(
rawState ? EditorState.createWithContent(convertFromRaw(rawState)) : EditorState.createEmpty()
);
// This runs separately to useState as it would always set cursor to left without the useState update first
const updateReduxState = (editorState) => {
const contentState = editorState.getCurrentContent();
const editorToJSONFormat = convertToRaw(contentState);
updateWidget({
...widget,
menu: { ...widget.menu, rawState: editorToJSONFormat },
});
};
const focusEditor = () => {
editor.current.focus();
};
return (
<>
<div className="RichEditor-root">
<div className={className} onClick={focusEditor}>
<Editor
editorState={editorState}
onChange={(editorState) => {
// ************ This is what fixed it for me, update the components setState and then redux after that ****************
setEditorState(editorState);
updateReduxState(editorState);
}}
ref={editor}
spellCheck={true}
/>
</div>
</div>
</>
);
};
const mapDispatchToProps = (dispatch) => ({
updateWidget: (widget) => dispatch(updateWidgetAndSaveState(widget)),
});
export default connect(null, mapDispatchToProps)(RichTextEditor);
Any updates on a working solution?
Also experiencing the same issue.
Using draft-js as a chat box, first comment everything is fine, but when you start typing on subsequent comments the cursor jumps to the start of the input after the first couple of characters have been typed. I've tried to apply suggestions above, but none of them work.
My solution is to save SelectionState in addition to RawDraftContentState and then deconstruct them both.
const editorContentWithSelection = {
rawCurrentContent: convertToRaw(editorState.getCurrentContent()),
selectionState: JSON.stringify(editorState.getSelection()),
};
// editorContentWithSelection to redux or server
and then deconstruct it like this:
// editorContentWithSelection from redux or server (having valid data)
const emptySelectionState = SelectionState.createEmpty("");
const parsedSelectionState = JSON.parse(editorContentWithSelection.selectionState);
const selectionState = emptySelectionState.merge(parsedSelectionState);
const editorState = EditorState.createWithContent(
convertFromRaw(editorContentWithSelection.rawCurrentContent)
);
return EditorState.forceSelection(editorState, selectionState);
I didn't have much luck with most of the above. For what's it's worth the easiest solution I found was to simply save the last correct selection state, let the editor move the cursor to the start, and then manually set the correct selection state after the incorrect editor state update.
So something like this after the cursor has been moved to the start.
const selection = editorState.getSelection()
if (selection.isCollapsed()) {
const selectionWithCorrectOffset = selection.merge({focusOffset: correctOffset, anchorOffset: correctOffset})
const newEditorState = EditorState.forceSelection(draftEditorState.draftState, updatedSelected)
}
Obviously, there is more to consider than just this but hopefully, this will point someone in the right direction.
Additionally, I maintain my own focus state as there are a number of edge cases that will cause Draft to have the incorrect focus state and therefore insert in the wrong location. In the case that Draft has the wrong focus state, I force it to use the correct one.
My solution is to insert some empty text after entering the library:
//Enter your new entity as per normal
const newContent = Modifier.insertText(
contentState,
selection,
" ", // note in my case it doesn't have any text
null,
entityKey
);
// enter a couple of spaces after the entity
const newContent2 = Modifier.insertText(
newContent,
newContent.getSelectionAfter(),
" ",
null,
null //it's just text
);
const nextState = EditorState.push(
editorState,
newContent2,
'insert-characters'
);
I ran into this issue when using a custom implementation of RichTextUtils.toggleInlineStyle and was able to achieve the desired behavior with:
if (selection.isCollapsed())
return EditorState.setInlineStyleOverride(
EditorState.forceSelection(editorState, selection),
newInlineStyle
)
Applying the forceSelection after setInlineStyleOverride does not work.
const newState = EditorState.createEmpty() this.setState({ editorState: EditorState.moveFocusToEnd(newState) })This worked for me...
This case just work in user edit in the end, if user edit in text of middle, cursor turn to the end, it's not the right way...
I too faced the same issue. so do we have a solution where content can be edited in the middle also
I am trying to achieve same behavior where I can edit at middle. Would you help me with that?
If you are finding that the selection moves somewhere unexpected after modifying the EditorState somehow, preserve the SelectionState before you modify your editorState and the forceSelection at the end of your logic.
selectionState = editorState.getSelection();
// perform some logic....
editorState = EditorState.forceSelection(editorState, selectionState)
const newState = EditorState.createEmpty() this.setState({ editorState: EditorState.moveFocusToEnd(newState) })This worked for me...
but if you have another input ,it cannot focus into other input box
What worked for me is checking if the current selection has focus before calling moveFocusToEnd
So
this.setState({
editorState: !editorState.getSelection().getHasFocus()
? EditorState.moveFocusToEnd(newState)
: newState
})
All the solution here doesn't work for me, and I noticed this happening after I load an already saved html, by converting to draft format as mentioned in the docs.
The closest was this const newState = EditorState.createEmpty() this.setState({ editorState: EditorState.moveFocusToEnd(newState) })
But it kept on automatically focusing it at the end no matter where I put the cursor on. Please does anybody have a fix for this
Is this problem still not resolved? Disappointed
This may or may not help someone. I was able to get past this issue by not updating the component level state until onBlur.
<Editor
editorState={rte}
onEditorStateChange={onValChange}
ref={rteRef}
onBlur={() => {
setPageContent({
...pageContent,
newPageContent,
});
}}
/>
I set my state as createEmpty on load:
const [rte, setRte] = useState(EditorState.createEmpty());
Set the state once the component mounts:
useEffect(() => {
const blocks = convertFromHTML(copy.body || '');
const state = customContentStateConverter( // converts for Hooks
ContentState.createFromBlockArray(blocks.contentBlocks, blocks.entityMap)
);
setRte(EditorState.createWithContent(state));
}, [copy.body]);
Update the state for only component updates. This is done so my useEffect does not always reset the location of the cursor:
const onValChange = useCallback((e) => {
setCopyHtml(draftToHtml(convertToRaw(e.getCurrentContent())));
setRte(e);
}, []);
I can use all the functionality of the Editor without weird cursor jumping. Then when I click outside of the Editor onBlur updates the Parent component.
OMG this library is a complete mess. So simple features and a huge complexity behind. And I can see was made by facebook engineers. I saw a guy in conferences introducing this library LOL He has a rock face XD. OMG I thought this thinks only happened in low level companies but as I can see Facebook enjoy complexity but not for analise algorithms XD I think good complexity is required in code interviews only at facebooks. XD
Im passing of this mess of lib. Good luck for those are using this mess.
I was facing this same issue, and ended up getting it working for my use case with a much simpler approach than many of the above examples.
TL;DR
Using moveFocusToEnd, then toggling Editor's readOnly prop fixed the issue for me.
Details
My use case
I have a component named RichEditor, which is my wrapper for Draft.js' Editor. RichEditor is used by many components in my app. Most of the instances of said components use RichEditor persistently; they sit there in read-only mode until you double-click them, at which point you can edit them. Read-only mode was implemented by having a readOnly prop in my RichEditor component, which is passed directly down to Editor's readOnly prop.
I also have "plain" (not using Draft.js) label elements which I want users to be able to edit by double-clicking on them. The double-click causes a popover to appear (which also contains RichEditor).
My issue was that for both the persistent components and the popover, the cursor always went to the beginning of the text after double-clicking them.
What worked for me
First, I was able to use one of the suggestions above with partial success:
setEditorState(editorState => EditorState.moveFocusToEnd(editorState));
This worked in getting the cursor to the end of the text upon the <Editor> component receiving focus. However, this introduced some maddening behaviors in my popover:
- The first time you hit the Backspace key after getting focus, it would delete the character immediately to the left of the cursor (as expected), but the cursor would then immediately shoot over to the beginning of all the text! Only once you've changed the cursor location after this (whether mouse-clicking or using arrow keys) would Backspace work fully as expected.
- Similar behavior when selecting some text via double-clicking a word or just plain click-dragging: the selection of text would work as expected, and the first character you typed would overwrite the selection with that character... but then immediately shoot the cursor over to the beginning of the text.
- When hitting Shift+Enter to create a newline (I've got my
handleKeyCommandconfigured that way), the newline would be appended after the cursor, instead of inserted before it. This would keep happening until you used the arrow keys (specifically) to change cursor location.
After spending an inordinate amount of time chasing this down (and trying some of the above suggestions with no success), I'd noticed that none of my persistent components were having these same new issues as the popover. Again, they're all using the same RichEditor component, so why the different behavior?
Well, it finally dawned on me that the difference between them is that the persistent components were having the readOnly value changed upon double-clicking (to invoke "edit" mode) - but I hadn't bothered with doing that for the popover since it would only ever be visible when meant to accept changes. With this thought, I started playing around with toggling readOnly in my popover, and voila - the weirdness described above all went away!
The tricky part of getting it sorted was to ensure that my popover (and ultimately, Editor) was fully rendered before changing readOnly from true to false. The way I implemented the fix was to add const [readOnly, setReadOnly] = useState(true); to my popover component, then in a useEffect() block I'd fire setReadOnly(false). The useEffect() block had a single variable in its dependency array, which was in my case a reliable way to determine whether it was time to fire setReadOnly.
I'm not sure why this works, but I suspect it's probably just a matter of triggering a re-render of Editor after moveFocusToEnd() was invoked. I didn't try any of the suggestions above involving EditorState.createEmpty(), as all instances of RichEditor have existing content, plus decorators and such. Maybe there's a simpler approach than this, but it's what worked for me.
@ggunnigle thanks for that write up! Totally fixed the jankyness of Draftjs for us. The local readOnly state value works super well.
@drock512 thank you, saved my day.
thank you all! i love you :) you saved me!
I want to let a comment here cause I had the same problem in production and the problem was I update the state with a custom handler:
const handleContentChange = (state) => { setEditorState(state) ....other process.... }
But If you read the doc at draftjs.org, you can see that they use setEditorState to directly update the state, I figured out with this:

Hope it'll help