DEV Community

loading...
Cover image for React boosting Textareas and Inputs.

React boosting Textareas and Inputs.

royletron profile image Darren ・3 min read

I was recently forced into the world of custom text editors again, after a fairly long hiatus. Text (especially rich text) has always been one of those areas that tools fall on either end of the 'too simple' to 'too complex' spectrum. Where simple tools don't offer what you need and complex ones offer too much. There is also the phenomena of 'it works but' where a requirement isn't ticked and you question whether you can convince stakeholders to drop it. My recent project was a simple input, that had a non-trivial view, but it had to work flawlessly. It looks like this:

preview

It allows the user to type free text and displays an emoji above each character as a secret 'code'. What I knew early on is it had to work like a real text input, it needed to support copy/paste and select, and line breaks and all that good stuff. The early ideas looked at content editable tags, and capturing keyboard events and doing all sorts of mad complicated stuff. In the end what I looked into was 'boosting' an input, a textarea in this case. By 'boosting' I mean taking the natural implementation and hooking things off of it to give it additional functionality. In this case, there is a textarea that isn't visible and the component controls focus as well as onChange and other events. Starting off with the usual:

  const textareaRef = createRef();
  const [value, setValue] = useState('');
Enter fullscreen mode Exit fullscreen mode

It then boils down to two functions that take care of keeping track of the value, and keeping track of the selection and focus.

  const onTextareaChange = (evt) => {
    onChange(evt.target.value);
    setSelection();
  };
  const setSelection = () => {
    setStart(textareaRef.current.selectionStart);
    setEnd(textareaRef.current.selectionEnd);
    setSelected(textareaRef.current === document.activeElement);
  };
Enter fullscreen mode Exit fullscreen mode

These are meant to be as general purpose as possible. The onTextareaChange is what you have all probably written dozens of times, but the setSelection is reasonably elegant. It sets three state values start, end and selected. Where start and end are the range of characters currently selected (also used to work out the position of my fake caret), and selected being a boolean showing focus. From here the rest of the component can do whatever it wants to show the 'output'. In my case I actually used click events on each 'letter' that it shows to change the selection:

  const onSelect = (idx = 0) => {
    if (onChange && !disabled) {
      textareaRef.current.focus();
      textareaRef.current.selectionStart = idx;
      textareaRef.current.selectionEnd = idx;
      setSelection();
    }
  };
Enter fullscreen mode Exit fullscreen mode

This will be updated to support drag selections eventually, but it simply expects an index as in the value index clicked. This will then ensure the textarea has focus and sets the selection back to trigger any other logic.

Generally this is pretty clean, and even with some additional animation is quite snappy in my particular use case.

demo

I also used the same technique for an input view, where the user has to decode the secret message. Which used individual input tags and some focus change magic:

demo_2

I'll clean up the code and share if people are interested, and you can go check out the finished app and write your own messages at mojimess, an app built for 6 year olds 😎

Discussion

pic
Editor guide