-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Typed char gets duplicated if also changing block type in onChange #92
Comments
This sounds like a use case for Does that work for you? |
Unfortunately not, I don't want to cancel the default behavior, just add too it. My use case is a markdown editor that styles the text as a way of syntax highlighting, but I still want the actual markdown shown to the user. |
I'd like to chime in on this one as well. I'm trying to make a Twitter post input (starting from the hashtag decorator example) and I'm looking to duplicate the behavior of Twitter's clients where it will highlight characters past 140 in red. After many fumbles, I settled on the technique of applying an inlineStyle to the overflow text. It works, except for the fact that every character I type into the control is duplicated. EDIT: On further experimentation, it's not consistently EVERY typed character that's duplicated. It appears the first typed character in a new block will not be duplicated. Here's my handleChange: handleChange: (editorState) ->
content = editorState.getCurrentContent()
okRange = makeSelectionStateFromRange(content, 0, 140)
console.log { okRange: okRange?.serialize() }
if okRange
content = Modifier.removeInlineStyle(content, okRange, 'OVERFLOW')
tooLongRange = makeSelectionStateFromRange(content, 141, 9999)
console.log { tooLongRange: tooLongRange?.serialize() }
if tooLongRange
content = Modifier.applyInlineStyle(content, tooLongRange, 'OVERFLOW')
@setState { editorState: EditorState.set(editorState, { currentContent: content }) } |
@wcjohnson I think the simplest is to use a decorator for that purpose. Something like: function past140Strategy(contentBlock, callback) {
const text = contentBlock.text();
if (text.length > 140) {
callback(140, text.length);
}
} |
@spicyj Your code above was the very first thing I tried, actually. I spent a lot of time trying to get it to work with decorators and I found that if the user puts a carriage return in the input, thus creating a new block, I couldn't make it work. I even tried writing a decorator strategy that would close over the contentState, and then I added up all the block lengths inside the strategy. That approach failed because the immutable.js contentState isn't updated until after all the strategies are already called... |
That seems consistent with my behavior that the first EDIT: Maybe it's not the changed state that become duplicated but instead the one after? Could explain the above sighting but I don't really know how to test and verify that :/ |
Further information: When I mutate the editorState in handleChange(), handleChange is called TWICE per keystroke -- the mutation inside handleChange is somehow provoking another change event. If I don't mutate the editorState in handleChange(), handleChange only gets called once. It appears Draft's internal model is expecting it to be in state X after calling onChange(X), and when it finds itself in X' instead of X, it fires another onChange. If that's really what's happening, it would seem to be a breakage of the standard 'controlled component' pattern. |
In cases like key entry at the start of a block, the native event is prevented and rendering is performed strictly by the
You can use
You should avoid using set/merge directly on I should probably guard against this.
Hmm, this should not be the case. Creating new state objects will have no impact on event handlers. Just to be clear, this is an |
@hellendag Yes, this is an onChange handler. Here is render: render: ->
<div className="post-editor">
<div className="inner-container" onClick={=> @refs?.editor?.focus()}>
<Editor ref="editor" editorState={@state.editorState} onChange={@handleChange}
placeholder="Enter content..." spellCheck stripPastedStyles
customStyleMap={styleMap} />
</div>
<input
onClick={this.logState}
type="button"
value="Log State"
/>
</div> With handleChange exactly as above, it is called twice per keystroke. With the following handleChange it is only called once per keystroke: handleChange: (editorState) ->
console.log "handleChange"
content = editorState.getCurrentContent()
# okRange = makeSelectionStateFromRange(content, 0, 140)
# console.log { okRange: okRange?.serialize() }
# if okRange
# content = Modifier.removeInlineStyle(content, okRange, 'OVERFLOW')
# tooLongRange = makeSelectionStateFromRange(content, 141, 9999)
# console.log { tooLongRange: tooLongRange?.serialize() }
# if tooLongRange
# content = Modifier.applyInlineStyle(content, tooLongRange, 'OVERFLOW')
@setState { editorState: EditorState.set(editorState, { currentContent: content }) } |
@hellendag Your suggestion about handleReturn will likely solve my problem. Thank you. I still feel there's something odd going on here, but if I don't have to deal with multiple blocks, then a decorator will work for me. |
I think I've got the answer here. I think the issue here is that the natively-rendered When you changed to a new API-wise, you shouldn't have to know about this stuff. That's my fault. I think the best thing to do here is to use a decorator, but I'll see if I can think of a better way to deal with this in the API. |
One straightforward option would be to offer a boolean prop to indicate that all rendering must be performed via React, with no native keystroke deferrals. Then you could do whatever you want in Drawbacks would be that spellcheck highlighting would flicker and we would pay re-rendering costs. Also that it's a bit of a hammer in the API. Continuing to think about it. |
Maybe another handler that if specified will be called before the native behavior is allowed. Just a thought, maybe a bit clumpy but better than flickering for applications that only occasionally will do style changes. |
FWIW, I'm attempting to address this in #453. |
Highlighting characters over a limit would be a good example. Many people want this (many Stack Overflow questions and jQuery plugins etc), and it requires a couple non-obvious tricks, like using RichUtils.insertSoftNewline. Attempting to do this any way other than with a decorator gets very messy and unless you know that inserting a soft newline prevents returns from creating new content blocks, you will have a bad time. |
Fixes facebookarchive#92. If an onChange handler replaces every "x" with an "abc", then when you type "x", we trigger onChange from beforeinput and rerender immediately with the content "abc", then the browser still inserts an "x" character! This fix is surprisingly simple: instead of triggering an update during beforeinput, we simply wait until the input event to do so. This means that when we rerender, the browser would have already inserted the "x" character and React will correctly delete it when rerendering. Test Plan: In plaintext example, change the handler to ``` this.onChange = (editorState) => { var content = editorState.getCurrentContent(); return this.setState({ editorState: EditorState.set(editorState, { currentContent: Modifier.removeInlineStyle(content, content.getSelectionAfter(), 'OVERFLOW'); }), }); }; ``` then type. Characters don't get duplicated like they did before.
Fixes #92. If an onChange handler replaces every "x" with an "abc", then when you type "x", we trigger onChange from beforeinput and rerender immediately with the content "abc", then the browser still inserts an "x" character! This fix is surprisingly simple: instead of triggering an update during beforeinput, we simply wait until the input event to do so. This means that when we rerender, the browser would have already inserted the "x" character and React will correctly delete it when rerendering. Test Plan: In plaintext example, change the handler to ``` this.onChange = (editorState) => { var content = editorState.getCurrentContent(); return this.setState({ editorState: EditorState.set(editorState, { currentContent: Modifier.removeInlineStyle(content, content.getSelectionAfter(), 'OVERFLOW'); }), }); }; ``` then type. Characters don't get duplicated like they did before.
…karchive#667) Fixes facebookarchive#92. If an onChange handler replaces every "x" with an "abc", then when you type "x", we trigger onChange from beforeinput and rerender immediately with the content "abc", then the browser still inserts an "x" character! This fix is surprisingly simple: instead of triggering an update during beforeinput, we simply wait until the input event to do so. This means that when we rerender, the browser would have already inserted the "x" character and React will correctly delete it when rerendering. Test Plan: In plaintext example, change the handler to ``` this.onChange = (editorState) => { var content = editorState.getCurrentContent(); return this.setState({ editorState: EditorState.set(editorState, { currentContent: Modifier.removeInlineStyle(content, content.getSelectionAfter(), 'OVERFLOW'); }), }); }; ``` then type. Characters don't get duplicated like they did before.
…karchive#667) Fixes facebookarchive#92. If an onChange handler replaces every "x" with an "abc", then when you type "x", we trigger onChange from beforeinput and rerender immediately with the content "abc", then the browser still inserts an "x" character! This fix is surprisingly simple: instead of triggering an update during beforeinput, we simply wait until the input event to do so. This means that when we rerender, the browser would have already inserted the "x" character and React will correctly delete it when rerendering. Test Plan: In plaintext example, change the handler to ``` this.onChange = (editorState) => { var content = editorState.getCurrentContent(); return this.setState({ editorState: EditorState.set(editorState, { currentContent: Modifier.removeInlineStyle(content, content.getSelectionAfter(), 'OVERFLOW'); }), }); }; ``` then type. Characters don't get duplicated like they did before.
I want to change the block type in onChange, however the typed character gets duplicated when doing that. Backspaces is not duplicated however so I can still remove one character at a time, not just type them.
Doesn't the editor support pushing another content change before being rerendered with its updated one?
The text was updated successfully, but these errors were encountered: