One of the weakest points of the web are the native form controls provided by web browsers. Although some great efforts are being done by browser vendors to improve what we get from plain old HTML, many useful elements are still missing.
One example of a pretty darn useful form input that is missing is a text area that grows in size along with its content. Most of the times when you have a text area in your web application, you don't actually know how much text your users are going to enter, and having your users deal with scrollbars when editing text provides them with a bad UX, especially on touchscreen devices.
The best solution to this problem is a textarea that adapts its size to its content, and in this article we are gonna be building exactly that. There are many packages available on npm that already solve this problem, such as react-textarea-autosize, but building your own stuff is always exciting, right? Outside of being able to customize the functionality of your component however you like, you will also be learning about how hooks and refs works in React. Sounds fun, doesn't it? 🙂
1. Creating the base component
We want to start with a simple component that returns a <textarea />. In order to use hooks, we are going to create a functional component. Whether you use the classical function syntax or the arrow functional one is up to you:
Let's go over a few details of this implementation:
- On the first line of the component we are creating a ref for the textarea element. This allows us to access its DOM node whenever we want and retrieve its properties. In our case, the scrollHeight property will be extremely useful;
- On the following lines we declare three state variables. Their names are pretty self explanatory;
- Next up we have the return statement with our JSX code. The reason we have a div enclosing the textarea is to prevent some scrolling problems that occur when we don't have it. We are also setting custom styles for the elements using the state variables.
2. Creating the onChange handler and useEffect callback
Now that we have our basic component that returns a textarea, we need to add the auto resize functionality:
When the textarea triggers an onChange event (i.e. when the user types something in it), our onChangeHandler sets the textarea's height to "auto", the parent div's height to the textArea's scrollHeight and updates the text state variable with the textarea's value. The reason we are resetting the textarea's height to auto is to ensure that it doesn't build up indefinitely in case the textarea has a padding greater than 0. Try commenting out that line, and see what happens when you enter any text:
Another odd thing we do is set the text variable with the textarea's content. We are doing this merely to keep track of when we should update the state variables that dictate the component's height, and this translates to adding a reference to the text variable on our useEffect's dependency array.
Finally, inside the useEffect hook, we set our textarea's height to its own scrollHeight. This happens after the onChangeHandler and before the next rerender, and even though technically the textarea is rendered with its size set to "auto" for a fraction of a second, your users won't notice it. What we can do however, is set some breakpoints on our code to see the action happening one step at a time.
3. Final touches
The final code we have should look something like this:
Some adjustments I have made to make the component even better are:
- Adding a props argument to the component, which is then passed to the textarea element, this ensures that our component can be used just like a plain old HTML textarea thoughout our website.
- Calling the onChange callback that is provided on the props object inside our onChangeHandler. This allows the onChange event handler to function normally from outside our custom component.
And voilà, let's look at the final result: