Here's a fiddle illustrating the behaviour: https://jsfiddle.net/4890mgej/4/
Code:
class MyEditor extends React.Component {
constructor(props) {
super(props)
this.state = {
state: Plain.deserialize('')
}
}
onChange(state) {
this.setState({ state })
}
onBeforeChange(state) {
return state.transform().setBlock('paragraph').apply()
}
render() {
return (
<Editor
placeholder="Enter some text..."
onChange={state => this.onChange(state)}
onBeforeChange={state => this.onBeforeChange(state)}
state={this.state.state}
/>
)
}
}
Hey @jatins, this is actually a UX issue, I'm curious to get your thoughts on. (And anyone else!)
Basically what's going on here is that for certain operations, (like inserting single text characters), Slate uses the isNative flag to avoid having to re-render, and instead just accept what is in the DOM, since these are cases where we know contenteditable will get the insertion right. This greatly improves performance since it avoids more re-renders.
But in this case, when using onBeforeChange and performing another transform, that flag is removed. This results in the original DOM event not having been preventDefault()'d, and also that the rendering logic adds the character too.
What are you looking to use onBeforeChange for here?
I'm considering removing it entirely since it leads to weird issues.
My use case is essentially a series of these steps:
onKeyDown which essentially gives me the state _before_ edit is applied)But also, I would want the typed character to be displayed as soon as user types (which I think won't happen if I preventDefault).
Also, that double character typed behaviour doesn't have much to do with onBeforeChange. If I do this inside my onChange I get the same behaviour.
onChange(state) {
this.setState({ state: state.transform().setBlock('paragraph').apply() })
}
I have a similar use case, where I need to setNodeByKey on a bunch of custom nodes to update their data (either onBeforeChange or onChange) and there are various reasons why a schema rule isn't well suited. Unfortunately I'm also getting the duplicate text issue when using either callback.
As with @jatins' example, being able to insert some transformations between when the next document state is computed from inputs and the render happens is occasionally very useful.
If onBeforeChange's days are numbered, what about an alternative with a more explicit purpose of finalising state—a blunter instrument than a schema rule—which can be used with the understanding that it might cause suboptimal re-renders?
If you're not bothered about summoning the dark lord of hacks, something like this could do the job...
const Plugin = (options) => {
let checksum
return {
schema: {
rules: [
{
match: node => node.kind === 'document',
validate: node => node.data.get('checksum') !== checksum ? node : null,
normalize: (transform, node) => transform
.setNodeByKey(node.key, { data: { checksum } })
.call(transformAllTheThings)
}
]
},
onBeforeChange() {
checksum = Math.floor(Math.random() * 1e16).toString(32)
}
}
}
💻 :trollface:
It doesn't play well with undo/redo though
I agree with @soulwire and onBeforeChange can be very useful when we want to transform the state before applying the state change. E.g. I have markdown editor, when I type something, I want to parse current block and see if it's an H1 block, then decide to update the current block type.
It looks like the root cause is that the onInput handle in content.js/div is triggered which inserts the character again.
It seems like if I just return the state in onBeforeInput handler, the double character issue is solved. See https://jsfiddle.net/f5gaL2c0/1/
The doc said if a state is returned from one of the plugin handlers, the other plugin will not be triggered. Does that mean core plugin's onBeforeInput is skipped in this case? Seems like core plugin is doing a lot of work in onBeforeInput https://github.com/ianstormtaylor/slate/blob/3eb72a86422acb543f518292a19965bb039b1604/src/plugins/core.js#L76. What's the implication on skipping it?
Should be fixed with isNative removed now!
Most helpful comment
I have a similar use case, where I need to
setNodeByKeyon a bunch of custom nodes to update their data (eitheronBeforeChangeoronChange) and there are various reasons why a schema rule isn't well suited. Unfortunately I'm also getting the duplicate text issue when using either callback.As with @jatins' example, being able to insert some transformations between when the next document state is computed from inputs and the render happens is occasionally very useful.
If onBeforeChange's days are numbered, what about an alternative with a more explicit purpose of finalising state—a blunter instrument than a schema rule—which can be used with the understanding that it might cause suboptimal re-renders?