Open jussch opened 8 years ago
Thanks for the details! Admittedly, this is a little tricky for me to follow.
Based on the error, my hunch is that swapping in the new EditorState
leads to some kind of mismatch between the DOM and the ContentState
, but it's hard to say.
Any chance you could provide a live example for me to try this on and get a better sense?
Thanks for the reply!
Unfortunately I don't know too much about the DraftJS code base, so I won't be of too much help, but I wrote up this example quick this morning: http://schultzjust.in/DraftJs-DualEditor/ . Hopefully this helps illustrate the problem. All the important code is in the HTML file.
Edit: (repo: https://github.com/jussch/DraftJs-DualEditor )
I am also seeing this issue. There is a timing window that exists from when a new editorState is passed into draftjs as a prop until that new state is rendered. If an onSelect
event fires in this time, then the code will throw. getUpdatedSelectionState
should detect this case and just return editorState.getSelection()
in this scenario.
This is a really annoying bug. Spams console with errors, breaks focus. Any chance we could pull in the fix from textioHQ/draft-js?
I've just updated to DraftJS v0.10.0 and while I haven't been able to extensively test this issue, I think the problem has been fixed. Thanks @AnilRedshift!
@jussch I'm still seeing this
Reopening because folks are still encountering this issue.
Also encounter this under ssr
Currently It is happening in ssr. So I temporary fixed that by delegating rendering of Editor to client side.
I have same problem with rendering Editor with server-side. I am using next.js framework.
@Huvik I am using next.js too. I solved like this:
import {Editor} from 'draft-js';
class Page1 extends Component {
constructor(props) {
super(props);
this.state = {
editor: null
};
}
componentDidMount() {
this.setState({
editor: withReduxForm(Editor),
});
}
render() {
return (
<div>
{this.state.editor}
</div>
)
}
}
export default Page1;
I bind editor at client side using componentDidMount
instead of ssr.
I got this when using draft inside block of other draft. How to fix this? Want to build table with draft in each cell
I would strongly recommend not attempting to implement a table in draft because it does not effectively deal with complex nested structures of that nature.
I'm also still seeing this
Just saw it now
I would strongly recommend not attempting to implement a table in draft because it does not effectively deal with complex nested structures of that nature.
I can confirm what @CameronAckermanSEL said - it's something we have not needed in the past at Facebook, and are just starting to look into. I don't expect we will focus on the editor-within-an-editor use case but I'd be happy to take PRs or examples of how to handle this use case better.
For implementing tables in Draft, there are folks working on this internally but it is still experimental. I expect it will take many months before we have anything ready to share in open source.
For anyone having trouble with this issue, specifically editing a readOnly
Editor and a live Editor at the same time, I found this trick solved it for me:
componentWillReceiveProps(nextProps) {
const { contentState } = nextProps;
if (
contentState &&
this.props.contentState !== contentState &&
this.state.editorState.getCurrentContent() !== contentState &&
this.editorState.getCurrentContent() !== contentState
) {
if (this.props.readOnly) {
const editorState = EditorState.createWithContent(contentState);
this.setState({ editorState });
return;
}
this.setState({
editorState: EditorState.push(
this.state.editorState,
contentState,
),
});
}
}
Quick context here: I built a wrapper editor for DraftJS that edits the contentState
rather than the editorState
so that I'm not storing selection state in my Redux. However, to achieve this, the wrapper editor has to convert contentState
to editorState
to use DraftJS's editor.
So then, to explain my code, if the editor is readOnly
, I avoid using EditorState.push(...)
because it forces browser selection to happen. This causes a issues if there is more than one editor editing the same contentState
. (or maybe its because its changing selection on a readOnly
editor, dunno) Instead, I use EditorState.createWithContent(contentState)
to generate my new editorState
, which bypasses the conflicting selection modification.
Maybe this can help anyone running in to a similar issue.
I can confirm that I get this error when trying to render Editor server side. I followed @davidnguyen179 way:
class MyEditor extends React.Component {
constructor(props) {
super(props)
this.state = {
editorState: EditorState.createEmpty(),
editor: null
}
this.onChange = editorState => this.setState({ editorState })
}
componentDidMount() {
this.setState({ editor: Editor })
}
render() {
const ClientEditor = this.state.editor
return (
<div>
{
this.state.editor ?
<ClientEditor
editorState={this.state.editorState}
onChange={this.onChange}
/> :
null
}
</div>
)
}
}
export default MyEditor
This works fine even with SSR. The thing is to render the Editor only when the component is mounted.
@jussch Could you please share the code of your wrapper editor? Really appreciate it! Have been struggling with this issue for a month now.
@flarnie Is there a ticket that we can follow to get update on table support in Draftjs? BTW, Slate JS can handle nested editor nicely. You guys may want to take a look at it. http://slatejs.org/
I don't know of an issue specifically for nested table support, go ahead and open one if you'd like. :)
https://github.com/facebook/draft-js/issues/600
https://github.com/facebook/draft-js/issues/220
https://github.com/facebook/draft-js/pull/388
There are more of these old issues and PRs that can be dug up from the backlog when searching for "Tables"
FWIW The author of #388 decided to move to Slate after the PR basically died, and went on to write a plugin that provides table support for Slate (out of courtesy I will not provide links to slate ecosystem on another project's backlog but google will help :))
Also, https://github.com/facebook/draft-js/pull/1084 has been opened for an eternity and resolves this problem. We had been using it for months before we migrated and it resolved this error in the table-via-nested-editor feature. (Once again I'm going to emphasize that nested draft editors to build a table is a bad idea)
@CameronAckermanSEL I know nested draft editor is tricky, but it looks like @bkniffler got it working and here is his demo site https://draft-wysiwyg.herokuapp.com/ I was trying to follow his thoughts with the latest draft, but got blocked by this issue due to editOnSelect got triggered for both editors even though one of them (the parent) is marked as readonly.
Hey @cupcoder, thanks for bringing up draft-wysiwyg. I've not been following the latest development of draft-js, but when I used it, nesting needed to be implemented in a very hacky way (focus management, events, drag&drop, etc don't work natively). There is no native tree-like data structure, but instead the document is flat.
I've found that using slate-js works much better in this specific case (nested editors with correct focus management, tree document structure). I've been able to use it to produce real nested, editable pages for a content-management system, with containers, accordions, etc.
@cupcoder I followed a similar approach and ran into the same problems that bkniffler listed above.
Here are some additional problems:
Draft is simply not designed to support a table workflow, which is a shame because it's a very common structure in documentation.
+1 same issue
To solve this I believe we need to another way to visualize this content is other routes as well.
Screenshots:
Below there is my code:
import React, { Component } from 'react'
import { actions } from 'mirrorx'
import Editor from 'draft-js-plugins-editor'
import createToolbarPlugin from 'draft-js-static-toolbar-plugin'
import 'draft-js-static-toolbar-plugin/lib/plugin.css'
import {
EditorState,
convertFromRaw,
convertToRaw,
} from 'draft-js'
import styles from '../config/editor-styles'
const staticToolbarPlugin = createToolbarPlugin()
const { Toolbar } = staticToolbarPlugin
let plugins = [staticToolbarPlugin]
const handleChangeContent = (content) => {
const contentState = content.getCurrentContent()
const rawContent = convertToRaw(contentState)
const currentContent = JSON.stringify(rawContent)
actions.editor.setState(currentContent)
}
class MyEditor extends Component {
constructor(props) {
super(props)
const { content, isReadOnly } = props
if (isReadOnly) {
plugins = []
}
const editorState = this._mountEditorState(content)
this.state = {
editorState,
}
this.onFocus = () => this.refs.editor.focus()
this.onChange = (editorState) => {
this.setState({ editorState })
handleChangeContent(editorState)
}
}
componentWillReceiveProps(nextProps) {
const { content, isReadOnly } = nextProps
if (content && isReadOnly) {
const editorState = this._mountEditorState(content)
this.setState({ editorState })
}
}
_mountEditorState(content) {
let editorState
try {
const rawContent = convertFromRaw(JSON.parse(content))
editorState = EditorState.createWithContent(rawContent)
} catch (err) {
editorState = EditorState.createEmpty()
}
return editorState
}
render() {
const { isReadOnly } = this.props
const { editorState } = this.state
return (
<div style={styles.root}>
{ !editorState && <div>Loading content...</div> }
<div style={styles.editor} onClick={this.onFocus}>
<Editor
readOnly={isReadOnly}
editorState={editorState}
onChange={this.onChange}
ref='editor'
spellCheck={true}
plugins={plugins}
/>
{ !isReadOnly && <Toolbar /> }
</div>
</div>
)
}
}
export default MyEditor
aha. I get rid of the error by using createFromBlockArray. Maybe you can try it.
this.state = {
editMode: false,
editorState: EditorState.createWithContent(ContentState.createFromBlockArray([this.props.block]))
}
And also I found that when I use onMouseDown to handle the sonEditor's focus event, the error never throwed. It seems that the getIn function is fired after the focus Event and is bound with onClick. So we need to let the sonEditor focus before onClick event. I tried onMouseDown ant it worked. Here is my code.
import React, { Component } from 'react'
import { Editor, EditorState, ContentState, Entity, convertFromRaw } from 'draft-js'
const emptyContentState = convertFromRaw({
entityMap: {},
blocks: [
{
text: '',
key: 'foo',
type: 'unstyled',
entityRanges: [],
},
],
})
export default class Caption extends Component {
constructor(props) {
super(props)
this.state = {
editMode: false,
editorState: EditorState.createWithContent(ContentState.createFromText(this._getValue()))
}
this.onChange = this._onChange.bind(this)
this._onClick = this._onClick.bind(this)
this._onBlur = this._onBlur.bind(this)
}
_onChange(editorState) {
this.setState({
editorState
})
}
_onClick() {
this.setState({
editMode: true,
}, () => {
this.props.onStartEdit()
this.caption.focus()
})
}
_onBlur() {
if (this.state.editMode) {
this.setState({
editMode: false
}, () => {
var entityKey = this.props.block.getEntityAt(0)
this.props.contentState.mergeEntityData(entityKey, { caption: this.state.editorState.getCurrentContent().getPlainText() })
this.props.onFinishEdit()
})
}
}
_getValue() {
return this.props.contentState
.getEntity(this.props.block.getEntityAt(0))
.getData()['caption']
}
componentDidMount() {
this._onClick()
}
render() {
return (
<div className="image-caption" onMouseDown={this._onClick} onBlur={this._onBlur} >
<Editor
textAlignment="center"
editorState={this.state.editorState}
onChange={this.onChange}
readOnly={!this.state.editMode}
ref={(ref) => this.caption = ref}
editorKey="caption"
/>
<style jsx global>{`
.pape-draft .image-caption:empty{
display:inline-block;
}
`}</style>
</div>
)
}
}
For those who use next-js, I've gotten around this issue by importing the Draft editor using next's dynamic import with ssr
turned off:
const Editor = dynamic(import("draft-js").then(module => module.Editor), {
ssr: false
});
This will obviously prevent server-side rendering 🙄
My scenario: I have two draftjs editors. One is the core input, and the other is a helper input which wraps its input in an entity and adds it to the core input. The core input and the helper input work as intended when the helper only adds to the core input on blur. The problem arrises when I tried to get the helper input to update the core input live, (updating both
editorStates
ononChange
of the helper input) I receive this error:I've boiled down my code as much as possible to find the perpetrator, and I've believe I've found the issue. Updating the
editorState
of the core Editor through the helper'seditorState
while the core Editor hasreadOnly={true}
triggers the error. (And withoutreadOnly={true}
, the core editor constantly takes focus from the helper input.)Here is the code to re-produce the error.
The above is a simplified version of my scenario, but it still illustrates the problem. Am I perhaps doing something wrong? Or is this a bug?
Thanks.