Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

react input cursor moves to the end after update

Tags:

reactjs

redux

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

like image 922
user3477051 Avatar asked Jul 19 '17 19:07

user3477051


4 Answers

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')
   }
}, []);
like image 83
Georgy Nemtsov Avatar answered Oct 09 '22 18:10

Georgy Nemtsov


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

like image 40
mavarazy Avatar answered Oct 09 '22 18:10

mavarazy


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.

like image 43
GlyphCat Avatar answered Oct 09 '22 19:10

GlyphCat


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})
}
like image 35
Mirza Usman Tahir Avatar answered Oct 09 '22 18:10

Mirza Usman Tahir