Как создать React компонент TextareaAutoSize с автоматическим изменением высоты
Мы рассмотрим, как создать компонент Textarea, который автоматически изменяет свою высоту в зависимости от количества введенного текста.
Готовый код доступен на GitHub
Определение констант и типов
Начнем с определения констант и типов, которые будут использоваться в нашем компоненте.
Эти константы задают значения для компенсации паддинга, начальной высоты строки прокрутки, максимального и начального количества строк.
Определим типы для значений и пропсов нашего компонента TextareaAutoSize.
Теперь напишем сам компонент - TextareaAutoSize
И добавим стили
Основные ключевые моменты без которых наша логика не будет нормально работать, это обязательное задание в стилях line-height, так как если не задать данный стиль то браузер вычислит его значение в normal, конечно же хук useAutoSize, который мы разберем ниже и атрибут style где мы сами управляем высотой нашего элемента.
Хук useAutoSize
Хук useAutoSize принимает два параметра:
- maxRows: Максимальное количество строк, после которого текстовое поле перестает увеличиваться по высоте и включается прокрутка. По умолчанию это значение равно MAX_ROWS.
- valueText: Объект, содержащий значение текстового поля.
Затем мы объявляем состояния и рефы:
- isOverflowAuto: Флаг для управления включением/выключением прокрутки.
- currentLineHeight: Текущая высота строки текстового поля.
- currentScrollHeight: Текущая высота прокрутки.
- textAreaHeight: Высота текстового поля.
- textAreaRef: Реф для текстового поля.
- currentMaxHeightRef: Максимальная высота текстового поля, вычисляется исходя из высоты строки и максимального количества строк.
Этот useEffect
выполняется при монтировании компонента и всякий раз, когда изменяется значение maxRows. В нем мы:
- Получаем высоту строки (lineHeight) из стилей текстового поля. Именно поэтому нам необходимо задать lineHight в px иначе браузер вернет normal и мы в итоге получим NaN.
- Получаем текущую высоту прокрутки (scrollHeight).
- Устанавливаем эти значения в соответствующие состояния.
- Вычисляем и сохраняем максимальную высоту текстового поля.
В хуке useLayoutEffect
мы измененяем высоту текстового поля при изменении текста. Кстати я выбрал useLayoutEffect потому что без него, так как мы постоянно при вводе текста делаем setTextAreaHeight("auto") браузер не успевает пересчитать обновленные значения и установить новую высоту для textarea и происходят сайдэффекты в виде дерганий поля. Можете сами попробовать.
Итак хук useLayoutEffect выполняется каждый раз при изменении текста в текстовом поле. В нем мы:
- Проверяем, если значение текстового поля пусто, то устанавливаем высоту поля равной текущей высоте прокрутки.
- Вычисляем текущее количество строк в текстовом поле.
- Если количество строк меньше максимального значения (currentMaxRows), то: Устанавливаем высоту текстового поля равной текущей высоте прокрутки. Отключаем прокрутку.
- Если количество строк больше или равно максимальному значению, то: Устанавливаем высоту текстового поля равной максимальной высоте. Включаем прокрутку.
Возвращаемые значения из хука useAutoSize
Хук возвращает объект с четырьмя значениями:
- isOverflowAuto: Флаг для управления включением/выключением прокрутки.
- setTextAreaHeight: Функция для установки высоты текстового поля.
- textAreaHeight: Текущая высота текстового поля.
- textAreaRef: Реф для текстового поля.
Хук useAutoSize можно использовать для управления высотой текстового поля в зависимости от количества введенного текста и для включения/выключения прокрутки.
Итог
В свое время у меня ушло много времени чтобы реализовать данный компонент для одного крупного PWA приложения, тогда еще не было chat GPT да и сейчас ради интереса я пробовал задавать ему такую задачу и он так и не справился в полной мере.
И я не мог нигде найти готовую реализацию под мои нужды, которую можно было бы легко расширять и кастомизировать. Поэтому я решил написать статью, вдруг есть кто-то кому будет полезна моя реализация или он подчерпнет основные идеи и сделает этот компонент еще лучше.