DEV Community

Cover image for 【React.js, Tailwind】Making an Dynamic Textarea
Kota Ito
Kota Ito

Posted on

【React.js, Tailwind】Making an Dynamic Textarea

I'm currently working on developing my first application page, which features a chat form using OpenAI's 'chat completions' API. This chat functionality is straightforward: it sends a POST request to the API with the user's message and retrieves the AI's response.

During the form creation process, I noticed that the textarea element doesn't automatically adjust its vertical size when a new line is added.

In this article, I want to share how I managed to create a textarea that dynamically adjusts its height whenever a user adds a new line.

How I implemented

First I came up with updating the textarea height using ref, useState and useEffect.

useEffect(() => {
  if (textareaRef.current) {
      textareaRef.current.style.height = 'auto';
      textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
        }
    }, [text]);

....

return (
 <textarea
   ref={textareaRef}
   value={text}
   onChange={handleChange}
   style={{ width: '300px', padding: '10px', boxSizing: 'border-box', overflow: 'hidden', resize: 'none' }}
 />
)

Enter fullscreen mode Exit fullscreen mode

In the code above, I just update the textarea style by extracting its scrollHeight and apply the style to the DOM.
However, it's generally recommended to avoid directly manipulating the DOM using refs and instead use state and props to control the UI.

So I needed to implement it another way and got a great idea from the internet.

Using Dummy Message element

<div className="w-full relative">
     <div className=" py-sm px-md overflow-y-hidden whitespace-pre-wrap  break-words max-h-[164px] min-h-[44px] invisible leading-[24px]">
       {message}
      {/* dummy text*/}|
     </div> // this div element is a dummy message element
     <textarea
       className="absolute right-0 top-0 bottom-0 left-0 py-sm px-md resize-none leading-[24px]"
       value={message}
       onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setMessage(e.target.value)}
      />
</div>
Enter fullscreen mode Exit fullscreen mode

In the code above, textarea is positioned absolutely within a parent div, allowing it to resize along with its parent.
This parent div also has another div tag, which is what we call "dummy message div".
this dummy div mirrors the content of the textarea, which is controlled by the message state.

As the user types in textarea, the message state updates, causing the dummy div to resize. This, in turn, resizes the parent div, allowing the textarea to adjust its size dynamically without displaying a vertical scrollbar.

whitespace-pre-wrap and dummy text

Maybe you realize that dummy div has this property "whitespace-pre-wrap" which is preserve newlines and spaces within an element. Also text will be wrapped normally.
Without this property, dummy message doesn't reflect the new lines and spaces that the user types in a textarea.
Additionally, it's important to have a dummy text under message in a dummy div. This ensures that the div maintains its line height when the user creates a new line.

Result

When a user breaks a new line, a textarea expand vertically.
Image description

Conclusion

I explored a common and effective technique for creating an auto-resizing textarea, which involves using a dummy div element. This approach particularly appealed to me because it avoids direct interaction with the DOM, a practice that is generally discouraged due to potential performance and maintainability issues.

However, one concern that arose was the potential impact on performance caused by updating the content of the dummy div on every keystroke. Despite this, I'm happy with the implementation.

Photo credit:
from Unsplash
by Volodymyr Hryshchenko

Top comments (0)