reduxjs / react-redux

Official React bindings for Redux
https://react-redux.js.org
MIT License
23.35k stars 3.38k forks source link

Connected text input cursor position reset on input #525

Closed thefloweringash closed 7 years ago

thefloweringash commented 7 years ago

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.

jimbolla commented 7 years ago

Hard to say without actually running another test battery. But I'd guess it's still is better perf in most cases because it still avoids setState/render calls unless final props have actually changed. Difference vs compat option off would be more more runs of mapStateToProps'n'friends and potentially extra renders if those extra runs produced different results between running with fresh props vs stale props.

gaearon commented 7 years ago

I would like to see another perf test before 5.x is released with these changes so that we don't have a situation where React Redux is slow but everyone already uses it and are waiting for React to bump to 16.

timdorr commented 7 years ago

Fixed via #540. Will be changed to default off when React 16 is released and we can push a 5.1 version.

istarkov commented 7 years ago

Hi guys,

I've read this thread and the source of connectAdvanced.js and I have one question, why not just move notifyNestedSubs from this line this.setState(dummyState, notifyNestedSubs) into componentDidUpdate

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 setState callback vs in componentDidUpdate ?

PS: I wanna say that setState callback in React@16 will be somehow similar to didUpdate, so if didUpdate solution above has some problems with current React version, looks like setState solution will have the same problems in React@16

jimbolla commented 7 years ago

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.

jimbolla commented 7 years ago

@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.

earlsioson commented 7 years ago

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 source: https://github.com/earlsioson/react-redux-input

derwaldgeist commented 7 years ago

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?

markerikson commented 7 years ago

@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?

markerikson commented 7 years ago

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?

jimbolla commented 7 years ago

Pretty sure this is totally fixed. Not sure what the exact version though.

derwaldgeist commented 7 years ago

Thanks for the info. Will check if I am on the latest versions of React and Redux, then.

linde12 commented 7 years ago

This worked for me when using react-redux@5.0.0-rc.1 but broke when i upgraded to react-redux@5.0.0-rc.2 where the initial fix was removed, and replaced by some componentWillUpdate fix. After upgrading to 5.0.0-rc.2 or anything higher, e.g. 5.0.5, i get the same behavior as before with the jumping cursor.

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.

jimbolla commented 7 years ago

@linde12 Can you provide a repro? I'm also using redux-form and it works fine.

linde12 commented 7 years ago

@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" <input> elements or other components that wrap inputs.

I'm doing something like this:

import {TextInput} from 'grommet';
const renderText = ({input}) => {
  // Try commenting out this block and un-commenting the other block
  return (
    <TextInput onDOMChange={e => input.onChange(e)} value={input.value} />
  );
  //return (
    //<input onChange={e => input.onChange(e)} value={input.value} />
  //);
}

const TestForm = ({handleSubmit}) =>
  <div>
    <form onSubmit={handleSubmit}>
      <Field
        name='Name'
        component={renderText}
      />
      <button type="submit">Ok</button>
    </form>
  </div>;

export default reduxForm({
  form: 'TestForm'
})(TestForm);

I'm using it with a empty reducer(apart from the form reducer provided from redux-form) and just a <Provider store={store}></Provider> wrawpped around the TestForm. I will try to dig deeper to see what's causing the problem, it might not have anything to do with react-redux. I will report back if i ever find out!

linde12 commented 7 years ago

@jimbolla I think i've found the culprit. In Grommet's onChange proxy they do a this.setState({...}) to update various things, which seems to trigger a re-render with the "old" input value because the new value wasn't quick enough to arrive from props. So when the new prop arrives there is a difference between the actual value in the <input> and the "new" value coming from props.

However, the fix in 5.0.0-rc.1 seemed to fix this. Any idea what to do next?

linde12 commented 7 years ago

@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:

  1. Type "aaaa"
  2. Click in the middle of the text, like so "aa|aa"
  3. Type "z"
  4. See that with react-redux@5.0.5 the cursor placement now is at the end("aazaa|")
  5. See that with react-redux@5.0.0-rc.1 the cursor placement stays in place("aaz|aa")

So the fix that was reverted in react-redux@5.0.0-rc2 (and replaced by another fix) worked.

Here are the two fiddles: react-redux@5.0.5 (bug shows) - https://jsfiddle.net/hqoohq4b/ react-redux@5.0.0-rc.1 (works) - https://jsfiddle.net/hqoohq4b/1/

derwaldgeist commented 7 years ago

I am not using Grommet and have the same problem with a plain vanilla <input>

linde12 commented 7 years ago

@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 react-redux@5.0.0-rc.1 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.

derwaldgeist commented 7 years ago

@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 react-redux@5.0.5. My app is running inside Electron, if that matters (it shouldn't, if you ask me).

linde12 commented 7 years ago

@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.