React: controlled input cursor jumps to end (again)

Created on 20 Feb 2019  路  14Comments  路  Source: facebook/react

Do you want to request a feature or report a bug?

Bug

What is the current behavior?

when typing in a controlled input, the cursor always jumps to the end. This was an old issue that seems to have resurfaced.

this code pen used in the docs here has the problem in all browsers as far as I have been able to test.

What is the expected behavior?

because we are using the state to update the component as soon as it's changed, the input element should be able to keep the cursor in the same place.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

I'm at latest (16.8.2) and I tested on Chrome, FireFox, and Edge on Windows

as far as I know, this was working at some point, though I don't know how long ago. possibly even before "Fiber"

Regression

Most helpful comment

I've updated from react 16.8.1 to 16.8.6. Still having the same problem...

All 14 comments

This is quite weird. Same code works for me on CodeSandbox: https://codesandbox.io/s/vqn79m09r3

Not sure what's going on here.

Seems UMD-specific.

Thank you very much for reporting this. It's a serious regression in the last patch release, and exposed some lacking coverage in our test suite regarding UMD bundles. I have a fix ready in https://github.com/facebook/react/pull/14914.

Great! thank you for the fix.

This is fixed in 16.8.3. Unfortunately unpkg doesn't seem to pick up the 16.8.3 version yet from its default redirect.

That's OK, I replaced the resources with the specific release artifacts to test and it works great now. 馃憤

This is quite weird. Same code works for me on CodeSandbox: https://codesandbox.io/s/vqn79m09r3

Not sure what's going on here.

@gaearon it flipped for me and I'm actually having the same problem OP reported in _your_ CodeSandbox, but _not_ in _his_ codepen. Should that be?

Chrome 74

I've updated from react 16.8.1 to 16.8.6. Still having the same problem...

I am running into this same issue. I have created the following fiddle which is a boiled down version of what I have.

https://jsfiddle.net/CodeMedic42/87f9gnow/

Ultimately once the event stack finishes it appears that React clears the value in the input since it is controlled. Then when the timeout fires it sets the correct value but at this time the position is lost.

I tried the fix this by handling the state internally to the TestInput Component as in the following fiddle.

https://jsfiddle.net/CodeMedic42/bt3w05m7/

But after debugging it appears that setState causes getDerivedStateFromProps to be called which has the old value and I end up in the same mess. I feel I need getDerivedStateFromProps because I have handle the case where the value can change externally and I need to set the state to this new value.

I believe if getDerivedStateFromProps was NOT called for a setState change then perhaps this problem would not exist, at least for my second example. But I am not familiar with the internals of React to even feel confident in this suggestion, so please take it with a grain of salt of course.

Okay based on my second fiddle I have come up with the following solution which should not require weird cursor manipulation. Please review, test, and use at your discretion. I have tested it a little and it appears to work. If I run into any problems I will post here.

https://jsfiddle.net/CodeMedic42/139sp08k/

Is this issue fixed? I'm having the same problem in 16.8.6.

Here is how I reproduce this issue. Is there something I'm doing wrong (or should do differently)?

const InputChild = props => {
  const [value, setValue] = useState("");
  useEffect(() => {
    if (props.value === value) return;
    setValue(props.value);
  }, [props.value, value]);

  return (
    <input
      placeholder="Enter text"
      type="text"
      value={value}
      onChange={e => {
        props.onChange(e.target.value);
      }}
    />
  );
};

const Input = () => {
  const [value, setValue] = useState("");
  return <InputChild value={value} onChange={setValue} />;
};

Sandbox

There are 2 possible ways to get around this issue, as far as I can tell.

Solution 1: Use the value from the prop.

<input
  type="text"
  value={props.value}
  onChange={e => {
    props.onChange(e.target.value);
  }}
/>

Solution 2: Update the local state simultaneously as the parent state.

<input
  type="text"
  value={props.value}
  onChange={e => {
    setState(e.target.value)
    props.onChange(e.target.value);
  }}
/>

Is there something I'm doing wrong

You're round-tripping the value change from Input to InputChild and back to Input and then calling setValue. Apparently this round-trip makes React forget/reset the cursor position because I assume it looses a association between value and input element and/or re-renders the whole component.

@gaearon suggested in https://github.com/facebook/react/issues/955#issuecomment-469352730 to call setValue directly in the component which does work, but is not ideal when one prefers a stateless component.

What I've done to workaround is to make the <input> uncontrolled (change value to defaultValue) with a onChange prop. This also brings the benefit of the input remaining responsive when onChange is computationally expensive.

Was this page helpful?
0 / 5 - 0 ratings