Hi.
I don't think if this is really a bug nor if this can be fixed easily but this is an annoying behavior.
See http://jsfiddle.net/kb3gN/4603/
The cursor jumps unexpectedly as the contenteditable element is rerendered.
Does this mean that we should always use contentEditable elements as uncontrolled components?
It works if the event handler reads the value like this:
emitChange: function(){
var html = this.target.value;
this.setState({html: html})
},
hmmm yes it seems so !!!
can you explain what's the difference?
Actually it works because of the error in my console Uncaught TypeError: Cannot read property 'value' of undefined
. The rerendering is never triggered so it's normal the caret does not jump, but it does not work actually because I need to update the state
The caret doesn't jump when you update value
(to the new value) because it has the HAS_SIDE_EFFECTS
flag in the React, so React polls the DOM before updating the value. This prevents the caret from jumping.
Polling innerHTML
would not make much practical sense at all I think, it could be prohibitively expensive and for most cases you wouldn't want it. I think there's some experimentation going on with controlled content-editables, but uncontrolled is the only sane way for now AFAIK.
Ah, yes, my answer was wrong.
The only idea I have now is to change shouldComponentUpdate like this:
shouldComponentUpdate: function(nextProps, nextState){
return nextState.html !== this.getDOMNode().innerHTML;
}
Then the cursor does not jump when the user types, But it still jumps when the change comes from the script.
It would be interesting to know if this could be done in a more controlled way.
@cody Updating from script inherently resets the cursor, there's no way for the browser to know where the cursor is in the new content. You need to get the cursor position before setting and restore it after.
PS. Also beware of _tons_ of undo bugs when you set from script.
Realistically I don't think there's anything React can/should do here, closing as wontfix
, reopen if you disagree.
@syranide I think it would be nice if React team did document good practices about dealing with contenteditables as it is quite hard to manage them well.
I think I've found a way to deal with them:
http://stackoverflow.com/questions/22677931/react-js-onchange-event-for-contenteditable/27255103#27255103
@cody if you want to manipulate the contenteditable from a script without jumping cursors, the best you can do is to update the DOM directly (I do this to "sanitize" rich pasted content). The cursor does not jump if you do not touch the nodes of the selection (I think, works for me)
I know this is an old closed issue but I wanted to chime in on a simple solution for people that run into it.
This only happens when you mutate the value so the DOM representation is different to a state value that's being set.
Simple solution - add a ref, mutate the ref's value to the manipulated one, then update the state. It's not quite as clean but it solves all the problems.
@abritinthebay yes this looks like what is proposed in the linked StackOverflow question and this project: https://github.com/lovasoa/react-contenteditable
For anyone else who, like me, ends up looking at this ticket because they think they need to roll their own contentEditable for a fancier text input: make sure to check out Facebook's DraftJS (and the 3rd-party DraftJS Plugins project)--might save you _a lot_ of time.
@clintharris Can we implement this in Vue?. If not then is there any way to add a single component of React inside Vue app?. Sound insane but I am also facing the same issue for 2 weeks. I am slowly giving up.
I know this is an old closed issue but I wanted to chime in on a simple solution for people that run into it.
This only happens when you mutate the value so the DOM representation is different to a state value that's being set.
Simple solution - add a ref, mutate the ref's value to the manipulated one, then update the state. It's not quite as clean but it solves all the problems.
This guy is awesome.
If you're still not clear that amazing solution, you could take my example
constructor(){
super();
this.ref = React.createRef();
}
onInput = (e) => {
this.ref.current.textContent = e.target.textContent;
}
render() {
<p ref={this.ref} onInput={this.onInput} contentEditable />
}
@sontuphan Your solution does not allow for two-way binding. Which is where problems arise.
Most helpful comment
I know this is an old closed issue but I wanted to chime in on a simple solution for people that run into it.
This only happens when you mutate the value so the DOM representation is different to a state value that's being set.
Simple solution - add a ref, mutate the ref's value to the manipulated one, then update the state. It's not quite as clean but it solves all the problems.