Использование в React-приложениях

Cleave.js используется как слой маскирования и форматирования ввода, но в React его применение требует учета модели управления состоянием компонента и жизненного цикла DOM-узлов. В React нельзя напрямую полагаться на императивное изменение DOM без синхронизации с состоянием, поэтому интеграция строится вокруг контролируемых компонентов и ссылок (ref).

Базовый сценарий установки:

npm install cleave.js

или

yarn add cleave.js

Подключение через ref и useEffect

Наиболее прямой способ интеграции — использование useRef и инициализация Cleave после монтирования элемента.

import React, { useEffect, useRef } from "react";
import Cleave from "cleave.js";

export default function PhoneInput() {
  const inputRef = useRef(null);
  const cleaveInstance = useRef(null);

  useEffect(() => {
    if (inputRef.current) {
      cleaveInstance.current = new Cleave(inputRef.current, {
        phone: true,
        phoneRegionCode: "KZ"
      });
    }

    return () => {
      if (cleaveInstance.current) {
        cleaveInstance.current.destroy();
        cleaveInstance.current = null;
      }
    };
  }, []);

  return <input ref={inputRef} type="text" />;
}

Ключевым моментом является обязательное уничтожение экземпляра при размонтировании компонента через destroy(), иначе возможны утечки памяти и неконсистентное поведение DOM.

Контролируемые и неконтролируемые подходы

Cleave.js по своей природе модифицирует значение input в DOM, что вступает в конфликт с полностью контролируемыми компонентами React.

Неконтролируемый input

В этом режиме React не управляет значением напрямую:

<input ref={inputRef} type="text" />

Cleave берет управление вводом на себя, изменяя отображаемое значение.

Контролируемый input

При использовании value и onChange возникает проблема двойного источника истины:

const [value, setValue] = useState("");
<input
  value={value}
  onCha nge={(e) => setValue(e.target.value)}
/>

Cleave при этом может конфликтовать с обновлением React, вызывая дергание значения или потерю форматирования. Поэтому требуется синхронизация через события Cleave.

Использование событий Cleave в React

Экземпляр Cleave предоставляет событие onValueChanged, которое позволяет синхронизировать состояние React с уже отформатированным значением.

import React, { useEffect, useRef, useState } from "react";
import Cleave from "cleave.js";

export default function CardInput() {
  const inputRef = useRef(null);
  const cleaveRef = useRef(null);
  const [value, setValue] = useState("");

  useEffect(() => {
    if (inputRef.current) {
      cleaveRef.current = new Cleave(inputRef.current, {
        creditCard: true,
        onValueChanged: function (e) {
          setValue(e.target.value);
        }
      });
    }

    return () => {
      cleaveRef.current?.destroy();
    };
  }, []);

  return <input ref={inputRef} value={value} type="text" />;
}

Такой подход делает React источником состояния, а Cleave — источником форматирования.

Обновление значения программно

В React часто требуется устанавливать значение извне (например, при загрузке данных). Cleave хранит «сырое» и «форматированное» значения отдельно, поэтому необходимо использовать API экземпляра.

cleaveRef.current.setRawValue("4111111111111111");

или

cleaveRef.current.setValue("4111 1111 1111 1111");

Различие между методами критично: setRawValue работает с исходными данными, setValue — с форматированными.

Получение значений из Cleave

Экземпляр предоставляет два основных метода:

const raw = cleaveRef.current.getRawValue();
const formatted = cleaveRef.current.getFormattedValue();

getRawValue() используется для отправки данных на сервер, так как возвращает очищенную строку без маски.

Динамическое изменение опций в React

React-компоненты часто требуют изменения конфигурации Cleave при изменении props. Например, смена типа ввода:

useEffect(() => {
  if (cleaveRef.current) {
    cleaveRef.current.destroy();

    cleaveRef.current = new Cleave(inputRef.current, {
      numeral: props.isNumber,
      date: props.isDate,
      datePattern: ["d", "m", "Y"]
    });
  }
}, [props.type]);

Такой подход предполагает пересоздание экземпляра при изменении критичных настроек.

Интеграция с формами

В React-формах Cleave используется как слой UI-форматирования, а не как источник бизнес-данных. Валидация и отправка происходят через состояние React:

function handleSubmit() {
  const rawValue = cleaveRef.current.getRawValue();

  fetch("/api/submit", {
    method: "POST",
    body: JSON.stringify({ card: rawValue })
  });
}

Форматированное значение не должно использоваться в логике сервера.

Обработка дат и чисел

Cleave поддерживает числовые и датовые маски, что особенно полезно в формах с финансовыми данными.

Числа

new Cleave(inputRef.current, {
  numeral: true,
  numeralThousandsGroupStyle: "thousand"
});

Даты

new Cleave(inputRef.current, {
  date: true,
  datePattern: ["d", "m", "Y"]
});

В React важно учитывать локализацию и возможные различия форматов, так как Cleave не выполняет автоматическую интернационализацию.

Сложные сценарии синхронизации

При использовании нескольких зависимых полей (например, сумма + комиссия) Cleave может требовать внешнего контроля перерендера:

useEffect(() => {
  if (cleaveRef.current) {
    cleaveRef.current.setRawValue(calculatedValue);
  }
}, [calculatedValue]);

Важно избегать бесконечных циклов обновления состояния: изменение Cleave → setState → rerender → повторная установка значения.

Очистка и уничтожение экземпляра

React StrictMode в development-режиме может вызывать двойное монтирование компонентов, поэтому корректное уничтожение Cleave критично:

return () => {
  if (cleaveRef.current) {
    cleaveRef.current.destroy();
    cleaveRef.current = null;
  }
};

Игнорирование этого шага приводит к дублированию обработчиков событий и некорректной работе input.

Типовые ошибки интеграции

Распространённые проблемы при использовании Cleave в React:

  • одновременное управление value и Cleave без синхронизации;
  • повторная инициализация без destroy();
  • использование formatted value вместо raw value для бизнес-логики;
  • отсутствие ref, что приводит к невозможности доступа к DOM-элементу;
  • обновление состояния внутри onValueChanged без оптимизации, вызывающее лишние ререндеры.

Архитектурный подход

Наиболее устойчивый подход в React-экосистеме заключается в разделении ролей:

  • React управляет состоянием данных;
  • Cleave управляет представлением ввода;
  • синхронизация происходит через onValueChanged;
  • наружу всегда экспортируется raw-значение.

Такая модель снижает связанность и предотвращает конфликт двух систем управления DOM.