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

Cursor jumps to end of input when onChange doesn't call setState #12762

Closed
danielrob opened this issue May 8, 2018 · 9 comments
Closed

Cursor jumps to end of input when onChange doesn't call setState #12762

danielrob opened this issue May 8, 2018 · 9 comments
Labels

Comments

@danielrob
Copy link

danielrob commented May 8, 2018

[Edit]: I'm asking for a feature.

Current Behaviour
An input onChange function that returns a value equalling the prior value causes the cursor to jump to the end of the input. This is the same as this comment from #995 formally raised as a feature request.

Repro sandbox: https://codesandbox.io/s/n4k3yx47j
That same code:

import React from "react";
import { render } from "react-dom";

class Input extends React.Component {
  state = { value: "TypeANumber" };

  onChange = e => {
    let nextValue = e.target.value;

    if (/[0-9]/.test(nextValue)) {
      nextValue = this.state.value;
    }
    this.setState({ value: nextValue });
  };

  render() {
    return (
      <input
        type="text"
        value={this.state.value}
        onChange={this.onChange}
      />
    );
  }
}

render(<Input />, document.getElementById("root"));

What is the expected behavior?
I'd like the cursor not to jump in the special case where the returned changed value is a rejected change i.e. the 'noop' change.

I understand fully that react cannot predict cursor position if the value is changed in onChange, however I cannot currently find an npm module that allows free-length regex filters (vs a fixed length mask) or a way to implement a filter myself, without the cursor jumping in this case.

[Edit]:
Since raising I now fully see this as a feature request for handling a special case of a behaviour that indeed is not a bug, differently. It would be a nice to have as it would allow very straightforward implementation of filters.

Regarding the non-clarity of how to deal with the general case of non-jumping cursors I think a modernized best practice example would be ideal, but that discussion still lives at #955.

I'd be totally fine with this issue being closed by assisting instead with the education of handling the general case. Though, this would still be a nice to have for the API, if possible.

@aweary
Copy link
Contributor

aweary commented May 11, 2018

Thanks for the request @danielrob! It would be nice if the selection state didn't jump when the input isn't updated. We do track and restore cursor position, but not if the element didn't change after a render cycle. This is because we restore selection mainly to account for the case where an input may be moved to somewhere else in the DOM.

We could consider relaxing that condition and always restore selection state if it exists. I'm not sure what the downsides to that would be.

For now, here's a workaround for your specific example: https://codesandbox.io/s/mq7n0wk0vp

You can store and update the selection range manually using componentDidUpdate.

@gaearon gaearon changed the title Cursor jumps to end of input when onChange returns an unchanged value Cursor jumps to end of input when onChange doesn't call setState Aug 6, 2018
@likerRr
Copy link

likerRr commented Sep 18, 2018

Faced the same issue. Any progress on it? Or may be any workaround of how we can call onChange without triggering setState?

@vinay72
Copy link

vinay72 commented Sep 25, 2018

According to me you would try with a

tags placed below the return( statement).

@Debananda
Copy link

Is there any update on the same. I am still facing the issue. My state are manage via redux so there are no this.setState()

@TheoryOfNekomata
Copy link

I have isolated this feature on CodePen. It is possible to track the cursor just before the input re-renders with transformed values.

https://codepen.io/theoryofnekomata/pen/JVVPbq

Please observe how disabling the "make it happen" parts of the code makes the cursor jump to the end of the input.

@stale
Copy link

stale bot commented Jan 10, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution.

@stale stale bot added the Resolution: Stale Automatically closed due to inactivity label Jan 10, 2020
@stale
Copy link

stale bot commented Jan 17, 2020

Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!

@bsaphier
Copy link

This issue still exists and I'm wondering what the solution is. In my application there are conditions where i don't want to allow a user to update an input

e.g.

function Input() {
  const [input, setInput] = useState('');
  const handleChange = (event) => {
    if (someCondition) {
      return; // cursor jumps to end of input
    }
    setInput(event.target.value);
  };
  return (
    <input onChange={handleChange} value={input} />
  )
}

I've also tried using setSelectionRange but the cursor still jumps to the end

i.e.

function Input() {
  const inputRef = useRef(null);
  const [input, setInput] = useState('');
  const handleChange = (event) => {
    if (someCondition) {
      inputRef.current.setSelectionRange(event.target.selectionStart, event.target.selectionStart);
      return; // cursor jumps to end of input
    }
    setInput(event.target.value);
  };
  return (
    <input ref={inputRef} onChange={handleChange} value={input} />
  )
}

@bsaphier
Copy link

Update:

Albeit a bit of a hacky solution the only workaround I've found is to wrap setSelectionRange in a setTimeout to ensure it fires after the change event

i.e.

function Input() {
  const inputRef = useRef(null);
  const [input, setInput] = useState('');
  const handleChange = (event) => {
    if (someCondition) {
      setTimeout(() => {
        inputRef.current.setSelectionRange(event.target.selectionStart, event.target.selectionStart);
      });
      return;
    }
    setInput(event.target.value);
  };
  return (
    <input ref={inputRef} onChange={handleChange} value={input} />
  )
}

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

No branches or pull requests

7 participants