Что происходит с таймером при каждом рендере в debounce хуке
Что проверяют
Понимаете ли вы, что компонент React это функция, которая выполняется заново на каждый рендер, и что всё объявленное внутри неё пересоздаётся. Вопрос про debounce это проверка на замыкания и на то, где вы храните значение, обязанное пережить рендер.
Как отвечать
Главное: идентификатор таймера должен жить вне тела функции-компонента. Дальше по вариантам.
- Локальная переменная
let timerвнутри компонента обнуляется на каждом рендере. СтарыйsetTimeoutпри этом не отменяется, а новый пишется в новую переменную, поэтому дебаунс молча перестаёт работать: колбэк срабатывает на каждое нажатие, просто с задержкой. - Правильное место это
useRef: объект рефа один и тот же между рендерами, значитtimerRef.currentпереживает их. Саму функцию тоже стоит стабилизировать, иначе она уйдёт новой ссылкой в зависимости эффектов и в мемоизированные дочерние компоненты.
Вариант с задержкой значения выглядит иначе, и таймер там действительно пересоздаётся:
useEffect(() => {
const id = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(id);
}, [value, delay]);Каждое новое value запускает новый таймер, функция очистки гасит предыдущий, и до паузы в вводе setDebounced не вызывается ни разу. Та же очистка отрабатывает при размонтировании.
Где спотыкаются
Говорят, что таймер создаётся на каждом рендере. В варианте с рефом таймер создаёт вызов обработчика, а не рендер, и путаница тут не безобидная: из неё растёт привычка чистить таймер в неправильном месте.
Замыкание устаревает. Колбэк помнит пропсы и состояние того рендера, в котором был создан, и через секунду отправит на сервер прошлое значение. Лечится рефом на свежий колбэк, который обновляется при каждом рендере.
Забывают очистку при размонтировании. Отложенный вызов дёргает состояние ушедшего компонента или шлёт запрос за закрытую страницу. Отдельно стоит помнить, что в режиме строгой проверки эффект в разработке прогоняется дважды, и реализация без очистки даёт двойной вызов, похожий на баг сервера.