Preserve cursor position when filtering out characters from a React input

Share this video with your friends

Send Tweet

If you've ever tried to filter out characters from the input in the onChange handler, you've most likely ran into the issue when, if you're inserting something in the middle of the input, the cursor jumps to the end of the input.

This happens because React has no way of knowing where you want the cursor to be!

In this lesson, we will create a filtering function that accepts new text and current cursor position, and which returns the filtered text and the new cursor position. We then use a custom useRunAfterUpdate hook after the set state call to update the cursor position after the component has re-rendered.

Aadil
Aadil
~ 6 years ago

This was an interesting insight into the technique. Could you kindly provide more insight into how the custom hook is used and functions (cold be another video) I only understand it at a rudimentary level.

As an experiment I've recreated this as a class component and wanted to check if this implementation is correct/best practice.

https://stackblitz.com/edit/react-2pwbkm

Gosha Arinich
Gosha Arinich(instructor)
~ 6 years ago

This was an interesting insight into the technique. Could you kindly provide more insight into how the custom hook is used and functions (cold be another video) I only understand it at a rudimentary level.

I'll probably work on a video about that sometime, but roughly, useRunAfterUpdate allows us to schedule an arbitrary function that will be run on component update. Your example is somewhat close, but there's a few issues: a) you don't move the cursor once, you're moving it on every update; b) your componentDidMount actually holds the code to move the cursor

A simple alternative to the useRunAfterUpdate hook in a class component would be just using the second argument to the this.setState function (it's an optional argument which accepts a callback that you want to be called after the state updates the component): this.setState({ value: cleanValue }, () => { input.selectionStart = cursor; input.selectionEnd = cursor })

Aadil
Aadil
~ 6 years ago

Hi Gosha, thanks for the feedback,

I've implemented a variation of the class component as per your recommendation as well as the example as you've detailed it in the lesson. You are correct, the cursor position is set on every subsequent render/update and that the functional component using hooks do not have this problem (see console logs). But adding a trace to your example on codesandbox

 runAfterUpdate(() => {
      console.log('set cursor')
      input.selectionStart = newCursor;
      input.selectionEnd = newCursor;
});

reveals that it updates on every input not only when the state is changed.

I also have unexpected behavior in my implementations. The cursor position is not maintained by the runAfterUpdate hook. I think this is because the useState update function (setName) performs a shallow comparison using Object.is and so will not trigger the update hook while the DOM element still receives input event and is updated outside reacts control (I hope that makes sense).

https://stackblitz.com/edit/react-2pwbkm