Skip to content
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

controlled input cursor jumps to end (again) #14904

Closed
ljcrapo opened this issue Feb 20, 2019 · 15 comments · Fixed by #14914
Closed

controlled input cursor jumps to end (again) #14904

ljcrapo opened this issue Feb 20, 2019 · 15 comments · Fixed by #14914

Comments

@ljcrapo
Copy link

ljcrapo commented Feb 20, 2019

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"

@ljcrapo ljcrapo changed the title input cursor jumps to end (again) controlled input cursor jumps to end (again) Feb 20, 2019
@gaearon
Copy link
Collaborator

gaearon commented Feb 21, 2019

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

Not sure what's going on here.

@gaearon
Copy link
Collaborator

gaearon commented Feb 21, 2019

Seems UMD-specific.

@gaearon
Copy link
Collaborator

gaearon commented Feb 21, 2019

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 #14914.

@ljcrapo
Copy link
Author

ljcrapo commented Feb 21, 2019

Great! thank you for the fix.

@gaearon
Copy link
Collaborator

gaearon commented Feb 21, 2019

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.

@ljcrapo
Copy link
Author

ljcrapo commented Feb 21, 2019

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

@ranneyd
Copy link

ranneyd commented May 29, 2019

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

@Manubi
Copy link

Manubi commented Jul 17, 2019

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

@CodeMedic42
Copy link

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.

@CodeMedic42
Copy link

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/

@volkanunsal
Copy link
Contributor

volkanunsal commented Aug 16, 2019

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

@volkanunsal
Copy link
Contributor

volkanunsal commented Aug 17, 2019

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

@volkanunsal
Copy link
Contributor

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);
  }}
/>

@silverwind
Copy link

silverwind commented Aug 17, 2019

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 #955 (comment) 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.

@nickmanning214
Copy link

nickmanning214 commented Jul 10, 2021

Is there something I'm doing wrong

In your example if you make a change, the InputChild component will be rendered once with outdated values. This is because the new value isn't set until useEffect. So whatever you typed will be erased for one frame, then put back after useEffect. This is enough to make the cursor position ambiguous to the browser.

As a hack suggestion, you can swap useEffect with useMemo. This works because useMemo runs before render.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants