-
Notifications
You must be signed in to change notification settings - Fork 47.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cursor jumps to end of controlled input #955
Comments
Your problem is that you're not rerendering with the new value. If you have React rerender with the new input value, then it'll know not to revert the value. (As it is, it will revert to the old value until it gets the new value after 50 ms. Changing 50 to a larger number like 1000 will make this much more apparent.) The following code should work: var ExampleApplication = React.createClass({
render: function() {
var model = this.props.model;
return <input onChange={this.nameChange} value={model.name} />;
},
nameChange: function(evt) {
this.props.model.name = evt.target.value;
rerender();
}
});
var myModel = {
name: 'Input is funky'
};
function rerender() {
React.renderComponent(
<ExampleApplication model={myModel} />,
document.getElementById('container')
);
}
setInterval(rerender, 50); (The normal way to do this when making a reusable component is to pass down a callback in |
Ah, thanks for the help! I'm not a big fan of passing a callback down, so I'm going to try and re-render from the parent immediately on model change events. |
Great -- that should work too. |
It requires synchronous redraws. See: facebook/react#955
I came across this problem when writing a Flux application. I updated the local state and dispatched an action to update the store. This local state update caused a re-render which still used the current "old" value in the store and thus changed the DOM and moved the cursor. This Stack Overflow answer helped me understand what was going on, so I thought I'd reference it here since this was the first result I found on Google, but didn't quite help me understand what was going on. |
@spicyj I'm seeing a similar issue of the cursor jumping to the end whenever I format / modify a controlled input's value between renders. Here's my code: export default class GiftPurchaseForm extends Component {
constructor(props) {
super(props);
this.state = {
cardNumber: '',
};
}
cardNumberChanged(event) {
this.state.cardNumber = event.target.value;
this.setState(this.state);
}
render() {
return (
<div id="gift-purchase-form">
<input
className="card-number"
type="text"
placeholder="Card Number"
value={creditcard.parse(this.state.cardNumber).formatted}
onChange={::this.cardNumberChanged}
/>
</div>
);
}
}
If I remove call to I'm on |
@tsheaff did you find a solution to this in the end? I'm also doing some onChange formatting to my text field. |
@tsheaff @mikeljames The problem is that React doesn't have enough information to do something intelligent. Assuming
(that is, between "1" and "8" in "4018"). Then I type a "2". Momentarily, the input looks like
but immediately,
Where should the cursor go? Even as a human looking at this, it's unclear: it could go before or after the space:
Figuring this out programmatically seems impossible to me in the general case. Even if we were content to return either of those, it's hard for me to imagine an algorithm that might work reliably. Let me know if I'm missing something. Because this is impossible and requires more knowledge about the specific problem space, React doesn't attempt to do anything intelligent with the cursor; you can set .selectionStart/.selectionEnd manually as appropriate for your domain. In the case of filtering out some characters it would be possible to write a more general solution but I think that may still be better left to a third-party component. |
Yes @mikeljames it's impossible in general as @spicyj explains. You can almost certainly do better than jumping to the end for your specific case of formatting using some simple heuristic. Doesn't make sense for React to burn extra cycles attempting to do this in general (e.g. formatting could be additive or subtractive). Perhaps linking this issue or a related discussion could be helpful in the official docs as I'd imagine formatting (phone numbers, credit cards, SSNs) is quite common. |
Actually: Stripe's jQuery.payment library preserves cursor position except if your cursor is already at the end, in which case it keeps your cursor at the end of the input. This generally feels pretty reasonable; try: http://stripe.github.io/jquery.payment/example/ That example flickers but that's not inherent to the strategy so we could do better. This might be more predictable in the common case, at the expense of introducing edge-case bugs because you now don't need to think about what should happen. For example, this strategy does feel broken in the case that you're typing before a space. If you have:
and type a 7 you have
which gets changed to
which feels wrong because your cursor should be after the 7, not before. But maybe this behavior is still better. cc @zpao for a second opinion. |
That is: I'm entertaining the idea of React's |
Wait: this wouldn't work at all for filtering out chars because your cursor would move over whenever you type an invalid character. Maintaining the distance from the end might work though? |
Here you can try it: http://jsbin.com/dunutajuqo/edit?js,output The numbers input works great. For the CC input: If you type "1234 5678" and then try to type a character after the space, your cursor is in the wrong place. Similarly, typing "1234 567" and then typing a character before the space does the wrong thing. |
Yes it's possible to do this well if React knows more about the format than simply what the value was and what it has changed to. Could borrow heavily from jQuery's mask plugin which is pretty great |
@tsheaff That's a good opportunity for a third-party plugin but we won't put it in the core. Maybe a "React mask component". :) |
Where do you draw the line on something like this? I agree that fully including/re-building jQuery Mask feels wrong & bloated, but it's unclear if there's a middle ground here that solves a meaningful part of the problem without introducing more unintuitive bugs. |
@tsheaff If you ask me, having an |
Any chance core can support the case where the input does not change and there's no selection? I think that's a pretty common edge case - user types an invalid character, so I reject the change. Currently this results in the cursor jumping to the end, when instead it'd be nice for the cursor to not move. |
My last example posted on Nov 30 keeps the cursor in the same place if you reject a change, at least. |
This is an excellent example... almost feel like it should be part of the main documentation. This was a weird issue to try and nail down in a controlled form with an attempted mask! |
Working around facebook/react#955 by forcing a synchronous rerender on each keystroke, instead of letting PersistentCharacterStore defer the publication of its state change.
Just wondering if there there is a react mask component people can recommend for this? |
@spicyj |
@Guria Thanks for pointing that out. |
@sophiebits I've posted @tibbus's request as a first class issue. Incidentally, all roads seem to lead back to this issue when searching in this area, and this issue seems to point to your comment on November 30th which no longer exists. While it seems understandable React cannot predict this case, and so simply places the cursor at the end, it would be ideal to have a best practice to overcome this when it is desired (to change the value after an onChange). I have tried many of the solutions posted above to no avail, perhaps due to the changes that react 16.3 brings. I've also found that workarounds of setting the cursor position manually conflict with React, perhaps my renders are too long, but even resetting the cursor position on the next tick causes a noticeable cursor jump. |
Since our state updates are async, it creates a race condition where React cannot correctly diff the updates and it results in the cursor being pushed to the end. See this React issue: facebook/react#955 In short, just another React annoyance. Fixes #237
…307) Since our state updates are async, it creates a race condition where React cannot correctly diff the updates and it results in the cursor being pushed to the end. See this React issue: facebook/react#955 In short, just another React annoyance. Fixes #237
It's slowing down development time 👎 |
I will try ant design kit to see if the issue is present. |
I don't know if this is still relevant, but I was able to solve this issue by just keeping track of the carat position and updating it when necessary. Note: This may or may not compile as-is. I pasted it from a more complex Component and whittled it down to the [mostly] bare essentials to show how it works.
This is an example of just disallowing certain types of strings to bet set as the value. For example, to limit the input to valid CSS selectors:
|
What do you think about this guys ? class App extends Component {
constructor(props){
super(props);
this.state = {
value: ""
};
}
change1 = (newValue) =>{
this.setState({value:newValue});
};
render(){
return (<CustomInput change1={this.change1} value={this.state.value}/>)
}
}
class CustomInput extends React.Component {
constructor(props){
super(props);
this.state = {
cursor: 0
};
this.textInput = React.createRef();
}
componentDidUpdate() {
if (this.textInput.current !== null)
this.textInput.current.selectionStart = this.textInput.current.selectionEnd = this.state.cursor;
}
change2 = (event) =>{
let cursor = event.target.selectionStart;
let value = event.target.value;
if (/^[ A-Za-z]*$/.test(value)) {
value = value.toUpperCase();
}else{
value = this.props.value;
cursor -= 1;
}
this.setState({cursor:cursor},()=>{
if (value !== this.props.value) this.props.change1(value);
});
};
render(){
return <input ref={this.textInput} onChange={this.change2} value={this.props.value}/>;
}
} |
@nerdo thanks, it helped. |
for me, cursor jumping started when I refactored from could it be a babel/webpack problem? (reproducible with React 16.4, react-autosuggest 9.4.2, react-scripts 1.1.5) edit: sorry, without |
Hi! this is my solution https://codesandbox.io/s/9208ol4r8y |
I don't see why you would use state here. This is not data that should cause your component to re-render. You just want to update the selection on your ref. I implemented it like this: constructor() {
super();
this.inputRef = React.createRef();
this.selection = {
start: false,
end: false
};
this.handleChange = this.handleChange.bind(this);
}
componentDidUpdate() {
const { selectionStart, selectionEnd } = this.inputRef.current;
const update = (this.selection.start !== false && this.selection.start !== selectionStart)
|| (this.selection.end !== false && this.selection.end !== selectionEnd);
if (update) {
this.inputRef.current.selectionStart = this.selection.start;
this.inputRef.current.selectionEnd = this.selection.end;
}
}
handleChange(event) {
const input = this.inputRef.current;
this.selection = {
start: input.selectionStart,
end: input.selectionEnd
};
this.props.onChange(event);
} Quick and simple. Works perfectly fine for me. |
Does anyone have any ideas for dealing with this on an input with type="email"? The problem is |
For anyone else looking for a solution, instead of changing type only during |
If anyone is still facing this problem, the easiest way is to preserve the cursor position before updating state and use the cursor position AFTER updating the state (not through callback)
|
Probably the smallest complete example with TypeScript: import * as React from "react";
import * as ReactDOM from "react-dom";
class App extends React.Component<{}, { text: string }> {
private textarea: React.RefObject<HTMLTextAreaElement>;
constructor(props) {
super(props);
this.state = { text: "" };
this.textarea = React.createRef();
}
handleChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
const cursor = e.target.selectionStart;
this.setState({ text: e.target.value }, () => {
if (this.textarea.current != null)
this.textarea.current.selectionEnd = cursor;
});
}
render() {
return (
<textarea
ref={this.textarea}
value={this.state.text}
onChange={this.handleChange.bind(this)}
/>
);
}
}
ReactDOM.render(<App />, document.getElementById("root")); |
@dhiraj1site I had to add onChange={(event) => {
event.persist()
const caretStart = event.target.selectionStart;
const caretEnd = event.target.selectionEnd;
// update the state and reset the caret
this.updateState();
event.target.setSelectionRange(caretStart, caretEnd);
}} |
I'm going to lock because a lot of new solutions in this thread look suspicious and likely point to other misunderstandings or bugs. The canonical solution is to make sure you're calling If it's not enough for you, please file a new issue with your reproducing case. |
When an input element is "controlled" by a model, the cursor will jump to the end of the line on every change. This makes it impossible to edit text that is not at the end of the input.
A quick demo: https://gist.github.com/ericvicenti/46f97f47c1cfe46040c8
It should be noted that this is only when using an external model, not when using the view's state. Maybe there is something wrong with my usage here?
As a suggested fix, maybe the input should not be overridden unless the value differs? Otherwise, the cursor position should be manually preserved.
Also, see this SO entry which documents the ability to grab and preserve the cursor position: http://stackoverflow.com/questions/1080532/prevent-default-behavior-in-text-input-while-pressing-arrow-up
The text was updated successfully, but these errors were encountered: