A bug.
Code:
`
class App extends React.Component {
state = {
value: initialValue,
currentState:'0',
}
onChange = ({ value }) => {
this.setState({ value })
}
onKeyDown = (event, editor, next) => {
//Insert text.
editor.insertText('11')
//Update state.
this.setState({currentState: '0'})
}
}
`
It's expected to insert text "11" when I press any key. But in fact above code only show one character that I press. After remove setState, everything is OK.
Update:
If I add a editor.flush() after insertText(), then when I press 'a', it first show '11a' and then an error page that shows: editor.flush() is not a function.
You forgot to add event.preventDefault() before you call editor.insertText('11')
You forgot to add
event.preventDefault()before you calleditor.insertText('11')
Yes, for above problem, adding event.preventDefault() will solve it. However my original version is more complex:
onKeyDown = (event, editor, next) => {
switch (this.state.currentState) {
//test state.
case '0':
//test key.
switch (event.key) {
case '1':
console.log("First:")
event.preventDefault()
editor.insertText('23')
this.setState({currentState: '0'})
console.log("Second.")
return next()
default:
console.log("state 0: default")
return next()
}
default:
console.log("state: default")
return next()
}
}
There is a nested switch that trying to mimic a state machine. When I press '1', above code show nothing . At the same time, there are console logs "First: " and "Second" so that this branch is indeed executed. When removing event.preventDefault(), a character that I press will present. I think the problem is related to setState().
After using a global variable to store currentState and remove setState(), the code runs as expected. But I'm still curious about the reason that above code fails.
Hi! I think I have the same issue:
https://jsfiddle.net/ma5f9z7t/32/
This also happens in my private project, but using hooks (useState) instead...
@Richard87 seems to be related to accessing this.state for the this.setState. If you change the code to either:
this.editor.wrapInline("link")
this.setState((prevState) => {triggered: !prevState.triggered})
or
this.setState({triggered:!this.state.triggered}, () => this.editor.wrapInline("link"))
it works. See https://jsfiddle.net/oyspn94k/
However, I'm not sure why this is the case.
I don't think it has anything to do with accessing this.state, I think it's how React is handling changes, and if you pass a function maybe it's better at merging the different setState's...
(If you just pass inn true you also have the same effect, passing a function works, passing the new value fails)
Unfortunatly, that quick fix doesn't solve the issue on my main project, only in the simplified reproduction :(
Here is a reproduction using React Hooks and _functional setState_:
https://codesandbox.io/s/4896x2nvk9
I found a workaround / super-hack, but it only works if you update state at the same time :fire:
https://codesandbox.io/s/6n2pznr6lk
const operations = useRef([]);
useEffect(() => {
while(operations.current.length > 0) {
operations.current.shift()()
}
})
const insertLink = testState => e => {
operations.current.push(() => editorRef.current.wrapInline("link"))
if (testState)
updateTest((prevTest) => !prevTest)
}
I ran into a similar problem. I think the issue might be that when you call setState, it triggers a re-render. Since value is stored in the component state and passed into Editor during render, this re-render will set the editor's value back to what was stored in the state. So any changes you made to the value via the editor's API will be overwritten.
In other words, there's a race condition between the editor's onChange callback that you use to update the value from the editor to the state and React's setState handler that renders the editor using the previous value in state.
Given this hypothesis, I tried to fix the issue by overwriting the value stored in state using the newly updated value from the editor, like this:
this.editor.wrapInline("link")
this.setState({
triggered: !triggered,
value: this.editor.value,
})
This resolved the issue for me. Hope it helps with your cases!
@ianstormtaylor: This might be worth documenting as a potential gotcha when controlling the editor's value. Or maybe exploring a mitigation strategy (or even just a loud error message) when the value is changed differently via the editor's API than via the editor's props.
Thanks for this thread. Yes, it is worth documenting. I am new to slate and spent several days struggling to figure out what I was doing wrong. Today, after reading this thread, I realized that I really wasn't doing anything wrong but that it was this state update issue that was interfering with my code... I now finally got my code to work with the !triggered kludge (I don't really fully understand why this solution works, but it does so I'm glad).
To give you some background, I was trying to open a modal dialog (in React) in order to edit all the link information (link text and link Url) via a single dialog. None of my Slate commands produced any effect. In order to close the modal dialog, I updated the state of the parent component and as a consequence, all the changes made to the value of the editor were discarded...
Ian, you might want to update the link example in slatejs.org instead of relying on two window.prompt to change the text and the URL of the link. I suppose that it would make the example a little more complex if you introduced a modal dialog, but this would tremendously help newcomers like me and avoid that they struggle with this state update issue.
To explain some of the difficulties a newcomer like me might have with Slate, I tried to use some transform and apply methods that I read about in another issue. Then I figured that this was deprecated . Then, I tried to figure out how Slate worked by looking at the code from slate-editor.bonde.org. That didn't help. The bonde code was written some other evolutions in Slate took place and Bonde relies on some change object that is now deprecated. I got errors that Slate had evolved since version 0.xxx where Editor.value.change could no longer be used and should be replaced by Editor.change. When I tried this, then I got another error that since version 0.47 Editor.change was also deprecated.
Opening a modal dialog to change the link parameters is something that many people might want to implement, so it would really help to have this state update issue documented somewhere else than in this thread. I well realize that this issue came up fairly recently (less than two months ago) but it really hurts....
Anyway, hello from Paris and Thank you for Slate. Despite the small troubles, it is a far better solution than CKEditor and the dozen other solutions that I tried before I found out about the existence of Slate...
With kind regards,
Another French user of Slate...
Sorry. It's me again. While I am able to achieve results with insertText and some other commands such as setBlock, it appears that insertInline, wrapInline, setInlines simply have no effects. Even when I did not use a modal dialog and copied the link example code from slateJS.org, nothing worked (I did replace the constant editor with the controller in this.editor.editor instead of this.editor). I also tried replacing the editor.command with wrapLink with the equivalent wrapInline but that still did not work.
function wrapLink(editor, href) {
editor.wrapInline({
type: 'link',
data: { href },
})
editor.moveToEnd()
}
/**
function unwrapLink(editor) {
editor.unwrapInline('link')
}
class HtmlEditor extends React.Component {
...
onClickInline= (event, type) => {
event.preventDefault()
const { editor } = this.editor.editor
const { value } = editor
const hasLinks = this.hasLinks()
if (hasLinks) {
editor.command(unwrapLink)
} else if (value.selection.isExpanded) {
const href = window.prompt('Enter the URL of the link:')
if (href == null) {
return
}
editor.command(wrapLink, href)
} else {
const href = window.prompt('Enter the URL of the link:')
if (href == null) {
return
}
const text = window.prompt('Enter the text for the link:')
console.log("texte:",text)
if (text == null) {
return
}
editor
.insertText(text)
.moveFocusBackward(text.length)
.command(wrapLink, "http://www.mycoachnutrition.com")
// this.setState({triggered:!this.state.triggered, value : this.editor.value})
}
}
I also wasnt able to obtain any results for selection commands (ex: moveAnchorBackward). The method editor.editor.value.selection.moveAnchorBackward(some integer) runs and doesn't throw errors but it doesn't do anything. Anyway, I found a workaround and used marks instead of inlines and editor.deleteBackward instead of changing the selection. I am running Slate 0.21.18 and saw that you are now at 0.21.20 so I tried to upgrade to see if the problem goes away but it didnt.
console.log("before = ",anchor.offset)
selection.moveAnchorBackward(1)
console.log("after = ",anchor.offset)
both console logs are identical.
As of https://github.com/ianstormtaylor/slate/pull/3093 (which was just merged), I believe this issue is no longer applicable, because a lot has changed. I'm going through and closing out any potential issues that are not out of date with the overhaul. Thanks for understanding.