Bug: cursor jumps to end of controlled <input> tag when value is modified
See original GitHub issueReact version: 16.13.1
Steps To Reproduce
- Make an
<input>
tag controlled, by setting itsvalue
in response toonChange
- Apply a transformation to the value (for example, replace spaces with underscores)
- Move cursor to the middle of the text and edit it
Link to code example:
https://gist.github.com/iain-merrick-fanduel/b9cea57baa9f20a5d288a0fcd6e7ee5e
Adapted from CodePen example (https://codepen.io/gaearon/pen/VmmPgp?editors=0010) on https://reactjs.org/docs/forms.html
The current behavior
If the transformation changes the value, the cursor is moved to the end of the input.
The expected behavior
Cursor should remain at the original position if possible (this is the behaviour of the TextInput
component in React Native).
Issue Analytics
- State:
- Created 3 years ago
- Reactions:1
- Comments:10 (1 by maintainers)
Top Results From Across the Web
React controlled input cursor jumps - Stack Overflow
If your value is controlled by state, React will maintain the input's cursor position. The problem is when the input receives a change...
Read more >Solving Caret Jumping in React Inputs - DEV Community
When you inject programmatically a different value in a DOM input, the input makes no assumption about caret position and moves it to...
Read more >The Curious Case of Cursor Jumping - Mutually Human
Cursor jumping is simply when the cursor jumps from one location to another, unexpectedly, while the user is typing into a text field....
Read more >React: why is my cursor jumping to the end of the input field ...
React: why is my cursor jumping to the end of the input field after the input is modified. It works ok but when...
Read more >Form Input | Components - BootstrapVue
The formatter must return the value as a string. Note: With non-lazy formatting, if the cursor is not at the end of the...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Well, that’s where you are wrong. It’s not that hard to implement this in React. Here is a demo: https://codesandbox.io/s/boring-dirac-utq82
I don’t agree with @gaearon for a common case. Possibly this is what people were complaining about in https://github.com/facebook/react/issues/955 or https://github.com/facebook/react/issues/5386
UPDATE: After re-reading this thread the OPs case is different from https://github.com/facebook/react/issues/955 and https://github.com/facebook/react/issues/5386 so this comment is misplaced. Happy to move this to any open issue which is better aligned or open a new one. I’ve left the original comment below for context…
Where an edit in the control caused the state change, the contents of the control are suited to preserving the selection. The value held in the control is already the value react is attempting to set. It originated in the control and may be taking the long way round owing to some async state-propagation mechanism. For this case, preserving the selection is a well-defined behaviour, and the ‘loss’ of selection may be considered an artefact of the recommended ‘controlled’ component binding approach combined with current react reconciliation behaviour.
One of two alternative strategies seem reasonable for reconciling an already-identical DOM value: a) Don’t set the value at all, the selection (cursor) will be implicitly preserved. b) DO restore the selection (cursor) after redundantly setting the value and therefore ‘breaking’ the selection. This is guaranteed to be a well-defined behaviour (and probably what was intended).
This assumes that state changes originated from a controlled component can’t ‘back up’. If a queue of changes sends a value from three keypresses ago, then two keypresses ago, then one keypress ago it will encounter a the dom value which is from the future, and can’t copy across the selection meaningfully.
I’ve shared the workaround below for discussion. It embodies selection-preserving strategy b) in the form of a hook. Doing it with strategy a) would involve monkey patching reconciliation internals.
The workaround was effective for my async state engine in simple interactive testing. Without it, every edit caused a loss of cursor position, even though it was reconciling to a value already in the DOM. I’d be curious if the approach works for others.
It can be used like…