'react input cursor moves to the end after update

When I update the value in my input field, the cursor moves to the end of the field, but I want it to stay where it is. What could be causing this issue?

<Input
  type="text"
  placeholder="test
  name="test"
  onChange={getOnChange(index)}
  value={testVal}/>

where Input is a component for the text input field, and getOnChange is:

const getOnChange = (index) =>
  (event) => props.onChangeTest(event, index);

This is then carried over to the parent component, where I dispatch to update the state via Redux. I can see that the state is being updated fine, but the problem is the cursor is not staying in position, and is always moving to the end of the text



Solution 1:[1]

If the cursor jumps to the end of the field it usually means that your component is remounting. It can happen because of key property change on each update of the value somewhere in your parent or changes in your components tree. It's hard to tell without seeing more code. Prevent remounting and the cursor should stop jumping.

Use this effect to track mounting/unmounting

useEffect(() => {
   console.log('mounted');

   return () => { 
       console.log('unmounted')
   }
}, []);

Solution 2:[2]

I would suggest using hooks to solve this

const Component = ({ onChange }) => {
  const [text, setText] = useState("");
  const isInitialRun = useRef(false);

  useEffect(() => {
     if (isInitialRun.current) {
        onChange(text);
     } else {
        isInitialRun.current = true;
     }
  }, [text]);

  // or if you want to have a delay

  useEffect(() => {
     if (isInitialRun.current) {
         const timeoutId = setTimeout(() => onChange(text), 500);
         return () => clearTimeout(timeoutId);
    } else {
         isInitialRun.current = true;
    }
  }, [text])

  return (
    <Input
        type="text"
        placeholder="test
        name="test"
        onChange={setText}
        value={text}/>
 );
}

To prevent initial call, when nothing changed isInitialRun used

Solution 3:[3]

This is the downside of the controlled component design pattern. I've been facing this problem for a long time and just lived with it. But there's an idea that I wanted to try in my spare time but end up never trying it (yet). Perhaps continuing with my idea could help you come up with the solution you need?

<Input
  type="text"
  placeholder="test
  name="test"
  onChange={getOnChange(index)}
  value={testVal}
/>


// From props.onChangeTest
const onChangeTest = (event, index) => {
  // TODO: Memorize the position of the cursor
  this.setState({ testVal: event.target.value })

  // Because setState is asynchronous
  setTimeout(() => {
    // TODO:
    // Programmatically move cursor back to the saved position
    // BUT it must increase/decrease based on number of characters added/removed
    // At the same time considering if the characters were removed before or after the position

    // Theoretically do-able, but it's very mind-blowing
    // to come up with a solution that can actually 'nail it'
  }, 0)
}


? If this is taking too much time and you just want to get work done and ship your app, you might wanna consider using the uncontrolled component design pattern instead.

Solution 4:[4]

I was facing same issue, it was due to 2 sequential setState statements. changing to single setState resolved the issue. Might be helpful for someone.

Code before fix:

const onChange = (val) => {
 // Some processing here
 this.setState({firstName: val}, () => {
  this.updateParentNode(val)
 })
}

const updateParentNode = (val) => {
  this.setState({selectedPerson: {firstName: val}})
}

Code After Fix

const onChange = (val) => {
// Some processing here
  this.updateParentNode(val)
}

const updateParentNode = (val) => {
  this.setState({selectedPerson: {firstName: val}, firstName: val})
}

Solution 5:[5]

You have two options.

  1. make it an uncontrolled input (you can not change the input value later)

  2. make it a properly controlled input

There is code missing here, so I can't say what the problem is. setState is not the issue: https://reactjs.org/docs/forms.html#controlled-components

If you use setState in the callback React should preserve the cursor position.

Can you give a more complete example? Is testVal a property that is manipulated from outside the component?

Solution 6:[6]

This totally worked for me (the other solutions did not):

const handleChange = (e, path, data) => {
    let value = _.isObject(data) ? data.value : data;
    let clonedState = { ...originalState };
    // save position of cursor
    const savedPos = e.target.selectionStart;
    _.set(clonedState, path, value); // setter from lodash/underscore
    // this wil move cursor to the end
    setState({ ...clonedState }); // some use state setter
    setTimeout(() => {
        // restore cursor position
        e.target.setSelectionRange(savedPos, savedPos);
    }, 0)
};

Have this on my template (using semantic-ui):

<Input
 type="text"
 readOnly={false}
 onChange={(e, data) => {
    handleChange(e, "field", data);
 }}
 value={state.field}>
</Input>

Solution 7:[7]

For me, I was having a <ComponentBasedOnType> and that was the issue, I changed my logic and render my components with a condition && in the parent component.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Georgy Nemtsov
Solution 2 mavarazy
Solution 3 GlyphCat
Solution 4 Mirza Usman Tahir
Solution 5 Markus
Solution 6
Solution 7 Rand Aldurayhim