Expanding a text area as you type is simple, right?... RIGHT?
I wasn’t even gonna write about this…
K… So the idea is to attach an onChange
event handler to <textarea />
,
look at the scrollheight
and adjust the style
accordingly… right?
Something like:
Almost there UX
export const ExpandingTextareaAlmostThereButNotQuite = () => {
const ref = useRef<HTMLTextAreaElement>(null)
const handleChange = () => {
const textarea = ref.current
if (textarea) {
textarea.style.height = `${textarea.scrollHeight}px`
}
}
return (
<textarea
ref={ref}
onChange={handleChange}
placeholder="Type something..."
/>
)
}
👉 Things seem to work fine until you start hitting backspace
A Better UX
export const ExpandingTextareaGoodStuff = ({
minHeight = 40,
maxHeight = 300
}: {
minHeight?: number
maxHeight?: number
}) => {
const [text, setText] = useState("")
const textareaRef = useRef<HTMLTextAreaElement>(null)
const adjustHeight = () => {
const textarea = textareaRef.current
if (textarea) {
// temporarily reduce height to trigger scrollHeight recalculation
textarea.style.height = `${minHeight}px`
// adjust to new scrollHeight
textarea.style.height = `${Math.min(textarea.scrollHeight, maxHeight)}px`
}
}
const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
setText(event.target.value)
adjustHeight()
}
return (
<textarea
ref={textareaRef}
value={text}
onChange={handleChange}
style={{
width: "100%",
minHeight,
overflowY: "hidden"
}}
placeholder="Type something..."
/>
)
}
##🍋 Why does this work
To make the <textarea>
expand and contract smoothly with its content, we need
to adjust its height based on the scrollHeight. The scrollHeight property
represents the total height of the content, including any part not visible due
to overflow.
However, when you delete content, setting the height to scrollHeight directly doesn’t always reflect the reduction correctly because the browser may not have recalculated the scrollHeight yet.
The Solution: We temporarily reduce the height of the <textarea>
to its minimum
height. This triggers the browser to recalculate the scrollHeight based on the
current content.