Hello, I'm experience some strange behavior when loading in a previous content state from my database. The cursor keeps backtracking randomly. Issue is illustrated here:

Here is my rather simple code:
class EditForm extends Component {
state = {
editorState: EditorState.createEmpty(),
contentState: null,
};
componentDidMount() {
fetch("api")
.then( res => res.json())
.then(data => this.setState({ contentState: JSON.parse(data) })
}
onContentStateChange = contentState => {
this.setState({
contentState
});
}
render(){
return(
<Editor
editorState={this.state.editorState}
contentState={this.state.contentState}
onContentStateChange={this.onContentStateChange}
/>
);
}
};
I couldn't find any examples in the docs regarding this.
@jonasgroendahl you are using both editorState and contentState props here. They make editor a controlled component. Since you are not updating editor state its messing up.
I would suggest that use only editorState and update it with changes in editor content.
Convert it to contentState only while saving in the DB.
Hello @jpuri, I'm having the same problem
I would like to have controlled editor.
class Editor extends Component {
onEditorStateChange(editorState) {
const textHtml = draftToHtml(convertToRaw(editorState.getCurrentContent()));
this.props.onChange(textHtml);
}
render() {
const contentBlock = htmlToDraft(this.props.value);
const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);
const editorState = EditorState.createWithContent(contentState);
return (
<Editor
editorState={editorState}
onEditorStateChange={state => this.onEditorStateChange(state)}
/>
);
}
}
In example above redux-form is passing html string through this.props.value.
Any suggestions? Thank you!
Using editorState={editorState} makes editor a controlled component, you need to ensure that you update it for each change in editor, check this: https://github.com/jpuri/react-draft-wysiwyg/tree/master/stories/BasicControlled
Or rather use: https://github.com/jpuri/react-draft-wysiwyg/blob/master/src/Editor/index.js#L50
Also, you can try
import {
EditorState,
} from "draft-js";
editorState = EditorState.moveSelectionToEnd(editorState);
Issue in this case may be that you are creating editor state each time from content state and content state does not saves selection.
I'm getting the same issue, and when i use EditorState.moveSelectionToEnd the cursor is always moved to the last when editing, so it's not possible to move the cursor back and edit the text it's always being moved to the last position. Any other way to solve this ?
What i'm trying to achieve is to have a controlled editor but in my case i need the content to be changed on componentWillReceiveProps:
componentWillReceiveProps (nextProps) {
const nextValue = nextProps.value
const { value } = this.props
if (nextValue !== value) {
const contentBlock = htmlToDraft(nextValue)
const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks)
const editorState = EditorState.createWithContent(contentState)
this.setState({
editorState: editorState
})
}
}
But after adding this code the cursor will move to the first position and start overriding the state. I can't find anything in the examples for an editor that needs to change their editorState when props change.
having Same issue Like @marlonmantilla.
@jpuri have same problem like @marcelometal and @nilaybrahmbhatt
In my case I get value in html-format from props of parent component and should update that value in parents state by 'this.props.onChange'
When I restart page, I get default value with help of 'componentWillReceiveProps' and I can put cursor whenever I want, but it's still moving it to the begining after every button click action
Here is my full component:
`
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
};
}
componentWillReceiveProps( nextProps ) {
const nextValue = nextProps.value
const { value } = this.props;
console.log(nextProps.value)
if (nextValue !== value) {
const contentBlock = htmlToDraft(nextValue)
const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks)
const editorState = EditorState.createWithContent(contentState)
this.setState({
editorState: editorState
})
}
}
onEditorStateChange = (editorState) => {
let html = draftToHtml( convertToRaw(editorState.getCurrentContent()))
if ( isFunction(this.props.onChange) ) { this.props.onChange(html) }
this.setState( { editorState } )
};
render() {
const { editorState } = this.state;
return (
<div>
<Editor
editorState={editorState}
onEditorStateChange={this.onEditorStateChange}
wrapperClassName="editor-wrapper"
editorClassName="editor-editor"
placeholder='Write down your text here ...'
/>
</div>
)
}
`
Could you please give us a hand? :)
@KipariS i've found that issues are caused by componentWillReceiveProps (since it's an anti pattern anyway more info here ) and ended up moving things to the constructor instead and using the key attribute to trigger mounting the component:
// wysiwyg editor component
constructor(props) {
super(props)
const contentBlock = htmlToDraft(props.value || '')
const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks)
this.state = {
editorState: EditorState.createWithContent(contentState),
focused: false
}
}
// parent
<WYSIWYGEditor
key={`editor-${id}`}
value={value} />
So whenever the id in the key attribute changes React will create a new component instead so you will get things initialized via constructor. Hope that helps!
@marlonmantilla Yep, it's good idea, but when my component mounting and constructor called, it still doesn't have value-data. So I need something to update it after I will receive value prop.
Hi guys, got the same issue but in my case, it only happens if I type fast enough (faster than normal typing), here is my code
import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { EditorState, convertToRaw, ContentState } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
import draftToHtml from 'draftjs-to-html';
import htmlToDraft from 'html-to-draftjs';
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import Wrapper from './Wrapper';
class TextEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
editorState: this.parseEditorState(props.value),
focused: false,
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.value && nextProps.value !== this.props.value) {
this.setState({ editorState: this.parseEditorState(nextProps.value) });
}
}
onFocus = () => this.setState({ focused: true });
onBlur = () => this.setState({ focused: false });
onEditorStateChange = (editorState) => {
this.setState({ editorState });
if (this.props.onValueChange) {
const content = draftToHtml(convertToRaw(editorState.getCurrentContent()));
this.props.onValueChange(content);
}
}
parseEditorState = (value) => {
const blocksFromHtml = htmlToDraft(value);
const { contentBlocks, entityMap } = blocksFromHtml;
const contentState = ContentState.createFromBlockArray(contentBlocks, entityMap);
return EditorState.createWithContent(contentState);
}
render() {
const { placeholder, value, onValueChange, readonly, ...rest } = this.props;
const { editorState, focused } = this.state;
return (
<Wrapper readonly={readonly} {...rest}>
<Editor
editorState={editorState}
wrapperClassName="___editor__wrapper"
toolbarClassName={cx('___editor__toolbar', { focused })}
editorClassName={cx('___editor__content', 'form-control', { focused })}
onEditorStateChange={this.onEditorStateChange}
onBlur={this.onBlur}
onFocus={this.onFocus}
placeholder={placeholder}
readOnly={readonly}
toolbar={{
options: ['inline'],
inline: {
options: ['bold', 'italic', 'underline'],
},
}}
/>
</Wrapper>
);
}
}
TextEditor.propTypes = {
placeholder: PropTypes.node,
value: PropTypes.string,
onValueChange: PropTypes.func,
success: PropTypes.any,
error: PropTypes.any,
readonly: PropTypes.bool,
};
TextEditor.defaultProps = {
value: '',
};
export default TextEditor;
Note: Wrapper component just for styling things
I solved it by changing editorState with defaultEditorState
<Editor
defaultEditorState={editorState}
wrapperClassName="___editor__wrapper"
toolbarClassName={cx('___editor__toolbar', { focused })}
editorClassName={cx('___editor__content', 'form-control', { focused })}
onEditorStateChange={this.onEditorStateChange}
onBlur={this.onBlur}
onFocus={this.onFocus}
placeholder={placeholder}
readOnly={readonly}
toolbar={{
options: ['inline'],
inline: {
options: ['bold', 'italic', 'underline'],
},
}}
/>
The cursor problem is gone, but new problem arise, the component cannot recieved initial value even though it was re-rendered
@archansel yes man, with added content from parent of component, it's not working.
I end up using key as @marlonmantilla suggested and it solved my problem
I solved it like this:
on editorState changed:
onEditorStateChange(editorState) {
this.setState({editorState})
const content = draftToHtml(convertToRaw(editorState.getCurrentContent())).trim().replace('<p></p>', '')
this.content = content
this.props.onContentChange && this.props.onContentChange(content)
}
on componentWillReceiveProps:
componentWillReceiveProps(nextProps) {
const newState = {}
if (nextProps.content && nextProps.content !== this.content) {
this.content = nextProps.content
newState.editorState = this.createEditorStateFromContent(nextProps.content)
}
Object.keys(newState).length > 0 && this.setState(newState)
}
createEditorStateFromContent(content) {
const contentBlock = htmlToDraft(content)
const contentState = ContentState.createFromBlockArray(contentBlock)
return EditorState.createWithContent(contentState)
}
After a lot of effort, i could integrate redux-form with react-draft-wysiwyg loading previus state..
thanks community.
MY COMPONENT
export class LabelAndTextArea extends Component {
constructor(props) {
super(props);
const editorState = EditorState.createEmpty();
this.state = {
editorState
};
this.changeValue(editorState);
}
static defaultProps = {
placeholder: "Seu texto aqui."
};
/**
/**
componentWillReceiveProps(nextProps) {
// this loads data from previus state.
const { input } = nextProps;
if (
input.value &&
input.value !== this.props.value &&
input.value !== "
onBlur(event) {
const value = draftToHtml(
convertToRaw(this.state.editorState.getCurrentContent())
);
this.props.input.onBlur(value);
}
/**
render() {
const { editorState } = this.state;
const { cols, name, label, placeholder, meta } = this.props;
return (
name={name}
wrapperClassName="border rounded"
editorClassName="ml-2"
className="form-control"
placeholder={placeholder}
onBlur={event => this.onBlur(event)}
onEditorStateChange={editorState => this.handleChange(editorState)}
// how to config: https://jpuri.github.io/react-draft-wysiwyg/#/docs
toolbar={{
options: ["inline", "list"],
inline: {
inDropdown: false,
options: ["bold", "italic", "underline", "strikethrough"]
},
list: { inDropdown: false, options: ["unordered", "ordered"] }
}}
/>
<ErrorAlert meta={meta} />
</Grid>
);
}
}
USAGE
label="Motivo"
name="motivo"
validate={[textAreaRequired]}
/>
I solved it like this:
on editorState changed:
onEditorStateChange(editorState) { this.setState({editorState}) const content = draftToHtml(convertToRaw(editorState.getCurrentContent())).trim().replace('<p></p>', '') this.content = content this.props.onContentChange && this.props.onContentChange(content) }on componentWillReceiveProps:
componentWillReceiveProps(nextProps) { const newState = {} if (nextProps.content && nextProps.content !== this.content) { this.content = nextProps.content newState.editorState = this.createEditorStateFromContent(nextProps.content) } Object.keys(newState).length > 0 && this.setState(newState) } createEditorStateFromContent(content) { const contentBlock = htmlToDraft(content) const contentState = ContentState.createFromBlockArray(contentBlock) return EditorState.createWithContent(contentState) }
there also has problem ,when i type fast ,
After a lot of effort, i could integrate redux-form with react-draft-wysiwyg loading previus state..
thanks community.
MY COMPONENT
export class LabelAndTextArea extends Component {
constructor(props) {
super(props);
const editorState = EditorState.createEmpty();
this.state = {
editorState
};
this.changeValue(editorState);
}static defaultProps = {
placeholder: "Seu texto aqui."
};/**
- Initialising the value for
*/
initEditorState() {
const html = "";
const contentBlock = htmlToDraft(html);
const contentState = ContentState.createFromBlockArray(
contentBlock.contentBlocks
);
return EditorState.createWithContent(contentState);
}/**
- This is used by to handle change
*/
handleChange(editorState) {
this.setState({ editorState });
this.changeValue(editorState);
}componentWillReceiveProps(nextProps) {
// this loads data from previus state.
const { input } = nextProps;
if (
input.value &&
input.value !== this.props.value &&
input.value !== "\n"
) {
const contentBlock = htmlToDraft(input.value);
const contentState = ContentState.createFromBlockArray(
contentBlock.contentBlocks
);
const editorState = EditorState.moveFocusToEnd(
EditorState.createWithContent(contentState)
);
this.setState({ editorState });
}
}
onBlur(event) {
const value = draftToHtml(
convertToRaw(this.state.editorState.getCurrentContent())
);
this.props.input.onBlur(value);
}/**
- This updates the redux-form wrapper
*/
changeValue(editorState) {
const value = draftToHtml(convertToRaw(editorState.getCurrentContent()));
this.props.input.onChange(value);
}render() {
const { editorState } = this.state;
const { cols, name, label, placeholder, meta } = this.props;
return ({label}
editorState={editorState}
name={name}
wrapperClassName="border rounded"
editorClassName="ml-2"
className="form-control"
placeholder={placeholder}
onBlur={event => this.onBlur(event)}
onEditorStateChange={editorState => this.handleChange(editorState)}
// how to config: https://jpuri.github.io/react-draft-wysiwyg/#/docstoolbar={{ options: ["inline", "list"], inline: { inDropdown: false, options: ["bold", "italic", "underline", "strikethrough"] }, list: { inDropdown: false, options: ["unordered", "ordered"] } }} /> <ErrorAlert meta={meta} /> </Grid> );}
}USAGE
do you have any other input except rich editor,eg input ????when i use EditorState.moveFocusToEnd,
it always stay in the rich editor end ,no longer focus in the upper input box

I solved it by changing
editorStatewithdefaultEditorState<Editor defaultEditorState={editorState} wrapperClassName="___editor__wrapper" toolbarClassName={cx('___editor__toolbar', { focused })} editorClassName={cx('___editor__content', 'form-control', { focused })} onEditorStateChange={this.onEditorStateChange} onBlur={this.onBlur} onFocus={this.onFocus} placeholder={placeholder} readOnly={readonly} toolbar={{ options: ['inline'], inline: { options: ['bold', 'italic', 'underline'], }, }} />The cursor problem is gone, but new problem arise, the component cannot recieved initial value even though it was re-rendered
Tnx it's working
when create new EditorState from content, the SelectionState is re-created, and the cursor position information (anchoroffset, focusOffset) will not kept, so need to update from old editorState.
for the EditorState.forceSelection and Editor.acceptSelection can refer to the link
static getDerivedStateFromProps(nextProps, state) {
if ('value' in nextProps) {
// get old editor state and old editor selection
let oldEditorState = state.editorState
const oldSelectionState = oldEditorState.getSelection();
// create new editorState from content
let content = nextProps.value
let contentBlock = htmlToDraft(content || "")
const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);
const newEditorState = EditorState.createWithContent(contentState);
// update cursor position base on old selection state
let updateSelection = newEditorState.getSelection().merge({
anchorOffset: oldSelectionState.getAnchorOffset(),
focusOffset: oldSelectionState.getFocusOffset(),
isBackward: false,
})
// check whether editor on focus, if onforcus use forceSelection to manually update the posiition
// if not on focus, use acceptSelection
let newEditorStateWithSelection;
if (oldSelectionState.getHasFocus()) {
newEditorStateWithSelection = EditorState.forceSelection(newEditorState, newEditorState.getSelection().merge(updateSelection))
} else {
newEditorStateWithSelection = EditorState.acceptSelection(newEditorState, newEditorState.getSelection().merge(updateSelection))
}
return {
editorState: newEditorStateWithSelection
};
}
}
onEditorStateChange = editorState => {
let value = draftToHtml(convertToRaw(editorState.getCurrentContent()))
this.setState({ editorState });
}
Most helpful comment
Tnx it's working