-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
Connected text input cursor position reset on input #525
Comments
Hmmm yeah good find. I can reproduce this in my own app. This seems related to facebook/react#955, and my first hunch is that changing the subscriptions from firing bottom-up to top-down reveals this behavior. Will need to investigate further. |
As an experiment, I wrapped an class Input extends React.Component {
constructor(props, ...args) {
super(props, ...args);
this.state = { value: props.value };
}
componentWillReceiveProps(nextProps) {
if (this.state.value !== nextProps.value) {
this.setState({ value: nextProps.value });
}
}
onChange(evt) {
this.setState({ value: evt.target.value }, () => this.props.onChange(evt));
}
render() {
return (<input {...this.props} {...this.state} onChange={this.onChange} />);
}
} |
I created a custom build of react-redux v5 that disables the top-down ordering so that subscriptions behave like v4. The cursor issue went away. So I think I know what the problem is, but I don't yet have a great solution. So far, the first idea I have is to provide another option for |
Ew. This... sounds nasty. |
@markerikson Indeed it is. As another experiment, I just tried switching React for Preact in my own project and the bug goes away. |
Is there a build of react master available somewhere? I'd like to test this against that because I know there are other bugfixes related to controlled inputs in master that are supposedly gonna land in react v16. |
@johnnycopperstone From what I gather from facebook/react#955, there are (at least) 2 scenarios in which this bug manifests itself, one being not updating the input's value prop soon enough (which is what I think is happening with react-redux v5) and one related to updating the prop with a value different than what's in the textbox, typically because of data altering (input masking or whitespace stripping, for example). Are you doing anything like that? |
Hey @jimbolla Sorry didn't see your reply - I just removed my comment as I realised it was a different issue and didn't want to pollute this issue. I was still using |
Well, Preact is pretty awesome, so I would suspect it's not going to see issues like this 😄 (Also, it doesn't have the huge event model that React does. Simpler code == less bugs)
I'm not sure what's in it, but |
@timdorr True, but our answer can't just be "use Preact instead" unfortunately. What I'm thinking...
|
Oh no, I'm not suggesting everyone use Preact, just that it's awesome in its own right. That's ancillary to this issue. |
IMO this will be extremely confusing and hurt the ecosystem. There are enough gotchas already, we should fix this in the library rather than add options. |
I think that if you update it while event is being handled, React should understand it. If not, it's a bug and I'm happy to look into it, given a pure React (no RR) reproducing case. |
I think I can produce a vanilla React repro. |
Here's a repro of the issue using redux + react-redux. I'm going to inline those to distill it down to vanilla react. import React, { Component } from 'react';
import { createStore } from 'redux';
import { Provider, connect } from 'react-redux';
const store = createStore((state = {}, action) => {
return action.payload
? action.payload
: state;
});
const Parent = connect(state => state)(props => props.children);
class BlockUpdates extends Component {
shouldComponentUpdate() { return false; }
render() { return this.props.children; }
}
const TextBox = connect(
state => ({ value: state.value }),
{
onChange: evt => ({
type: 'CHANGE',
payload: { value: evt.target.value }
})
}
)(props => (<input type="text" id="demo" {...props} />));
const App = () =>(
<Provider store={store}>
<Parent>
<BlockUpdates>
<form>
<TextBox />
</form>
</BlockUpdates>
</Parent>
</Provider>
);
export default App; |
And here's the vanilla React version, that reduces (no pun intended) the issue down to its core: import React, { Component } from 'react';
let currentState = { value: '' };
class BlockUpdates extends Component {
shouldComponentUpdate() { return false; }
render() { return this.props.children; }
}
class TextBox extends Component {
render() {
return (
<input
type="text"
id="demo"
onChange={evt => this.props.setState({ value: evt.target.value })}
value={currentState.value}
/>
);
}
}
class App extends Component {
render() {
return (
<form>
<BlockUpdates>
<TextBox
ref={c => this.textbox = c}
setState={newState => {
const makeTheCursorJump = true;
currentState = newState;
if (makeTheCursorJump) {
// my guess is that the code that affects the cursor position executes before
// setState's callback, meaning the callback code doesn't get a chance to be
// a part of that process.
this.setState({}, () => this.textbox.setState({}));
} else {
// if the textbox updates first
this.textbox.setState({});
this.setState({});
}
}}
/>
</BlockUpdates>
</form>
);
}
}
export default App; |
@gaearon import React from 'react';
const format = value => {
// any transform here
return value.replace(/\d/g, 'a')
};
class HelloWorld extends React.Component {
constructor(props) {
super(props)
this.state = {
value: '0000000'
}
}
render () {
return <input value={this.state.value} onChange={e => this.setState({
value: format(e.target.value)
})} />
}
}
export default HelloWorld; |
@Guria If you change the value right after input, the cursor jump is expected. React can't guess where to put the cursor. So that is not a bug. |
@jimbolla This does not look like a bug to me. It is documented that |
@gaearon But It's happening as part of the callback of setState(), which should have the new state. But even still, if I store the state in a global variable and read from there instead of component state, it behaves the same way. This seems more related to when React reconciles the current value of the input's value prop with what's actually in the DOM element. Basically, by the time the callback to setState fires, it's too late. I can update the code to make this more clear. |
Oh okay. |
It would help my understanding if you showed a snippet with a global variable. The smaller example the better. |
Ouch, forgot about that render caused by setState is not synced with event. So my example definetely invalid.
Then is there a right way to make masked value with controlled input? Looks like an overkill to make class component here when it is just about transorming a value. |
@gaearon I updated my above example to use global variable. You can toggle the |
It's a bit hard to tell what's going on but I can look deeper into it. My intuition is that you should almost never use Could you explain why |
Fixed via #540. Will be changed to default off when React 16 is released and we can push a 5.1 version. |
Hi guys, I've read this thread and the source of So you will get something like subscription.onStateChange = function onStateChange() {
...
if (!this.selector.shouldComponentUpdate) {
subscription.notifyNestedSubs()
} else {
this.callOnDidUpdate = true;
this.setState(dummyState)
}
}
....
componentDidUpdate() {
if (this.callOnDidUpdate) {
notifyNestedSubs()
this.callOnDidUpdate = false
}
} I've created jsbin example based on vanila React example and it has no cursor bug. What the real need to call nested subs notification in PS: I wanna say that setState callback in React@16 will be somehow similar to didUpdate, |
Hmmmm. I could swear this did not work when I was testing it. But here it is. Well, I'll have to give this another go. Assuming this doesn't have significant perf impact or any other problems, this would eliminate the need for that stop-gap setting. |
…mponentDidUpdate. (#557) * Fixes #525 (cursor bug) by moving notify from setState callback to cDU. This also eliminates react15CompatibilityMode stopgap setting. See: #525 (comment) for discussion. * Optimizes perf of previous commit.
@spicyj I was just wondering if you were able to make an automated test from this. If so, I'd like to see it for my own education. |
Hello, before posting a separate issue, can anyone here confirm that the behavior demonstrated in the following demo site is related to this issue... To replicate the behavior, insert cursor after 42.00 in the input field. Hit the delete key twice and notice how the cursor jumps to the beginning of the input field. demo site: https://earlsioson.github.io/react-redux-input |
I just stumbled upon this problem and am quite confused after having read most of these comments here. Has this problem been fixed in the core somehow, and if yes, in which version? Or do I still need a kind of hack to prevent the cursor from jumping to the end of the input field? |
@derwaldgeist : It should be fixed with the current "latest" versions of React and React-Redux. Are you seeing this behavior? If so, what versions are you using now? |
I take that back. If I'm reading this correctly, the change on the React side that resolves this is merged in, but won't be released until 16.0? |
Pretty sure this is totally fixed. Not sure what the exact version though. |
Thanks for the info. Will check if I am on the latest versions of React and Redux, then. |
This worked for me when using I'm not sure if this has anything to do with other libraries that interfere somehow. I'm using redux-form to update my field values, so maybe it has to do with that. I'm having a hard time seeing how that would change anything, but it might. I will try to isolate the problem. |
@linde12 Can you provide a repro? I'm also using redux-form and it works fine. |
@jimbolla I've narrowed down the problem and slimmed down the code and it seems to be in combination with the UX library Grommet, it doesn't happen with "vanilla" I'm doing something like this:
I'm using it with a empty reducer(apart from the form reducer provided from redux-form) and just a |
@jimbolla I think i've found the culprit. In Grommet's However, the fix in 5.0.0-rc.1 seemed to fix this. Any idea what to do next? |
@jimbolla (and anyone else interested) I made a fiddle demonstrating the problem. You can try typing anything(e.g. "aaaa") and put your cursor in the middle("aa|aa") and type something else(e.g. "z") which results in "aazaa|". Steps to reproduce:
So the fix that was reverted in Here are the two fiddles: |
I am not using Grommet and have the same problem with a plain vanilla |
@derwaldgeist Are you manipulating state(with setState) in someway? Or are you formatting your input(because that would probably require you to keep track of the cursor yourself somehow)? Is there any reason why the fix in [email protected] was replaced? Is it something we can take in today? If yes, i'd be happy to help as we are currently considering what options we have to fix this. |
@linde12 Nope, I am just using Redux to update the store based on user input (i.e. in an onChange handler). I am currently using [email protected]. My app is running inside Electron, if that matters (it shouldn't, if you ask me). |
@derwaldgeist I don't think it should either, do you have some code where you can reproduce the problem? Maybe i can verify if it's similar to the problem i am experiencing. |
…k to componentDidUpdate. (reduxjs#557) * Fixes reduxjs#525 (cursor bug) by moving notify from setState callback to cDU. This also eliminates react15CompatibilityMode stopgap setting. See: reduxjs#525 (comment) for discussion. * Optimizes perf of previous commit.
I have an input field connected to a part of a redux store. When I type in the text field the cursor position is reset to the end of the text field. For example filling in "123", then attempting to insert "abc" at the beginning results in "a123bc". I've been unable to isolate the exact circumstances that cause this, but have a small reproduction at https://jsfiddle.net/1fm2cczq/. Occurs on 5.0.0-beta.3. Does not occur on 4.4.5.
The text was updated successfully, but these errors were encountered: