UseRef в React: фокус на поле и значение, которое не вызывает рендер

UseRef в React: фокус на поле и значение, которое не вызывает рендер
Содержание статьи

useRef возвращает объект с полем current, который сохраняется между рендерами компонента. Изменение current не запускает новый рендер. Поэтому ref подходит для ссылки на DOM-элемент и служебных значений, от которых не зависит отображаемый интерфейс. Для видимого текста, счётчика или состояния кнопки обычно нужен useState.

В примере Mentoring.digital кнопка переводит фокус в поле поиска. Она не отправляет форму и не выполняет сетевой запрос.

Как перевести фокус на input

import { useRef } from "react";

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

  function focusSearch() {
    inputRef.current?.focus();
  }

  return (
    <div>
      <label htmlFor="search-query">Поисковый запрос</label>
      <input id="search-query" ref={inputRef} />
      <button type="button" onClick={focusSearch}>
        Перейти к поиску
      </button>
    </div>
  );
}

React запишет DOM-узел в inputRef.current после его добавления в интерфейс. До этого значение равно null. При удалении узла React снова установит null. Необязательная цепочка ?. защищает вызов, если элемента уже нет.

Тип кнопки указан явно, чтобы при переносе примера внутрь формы она случайно не стала кнопкой отправки. Метка через htmlFor остаётся связана с полем. Фокус должен быть заметен пользователю клавиатуры, поэтому не убирайте стандартную обводку без равноценного видимого оформления.

Почему ref не заменяет состояние

import { useRef, useState } from "react";

export default function ClickTracker() {
  const lastClickAt = useRef(null);
  const [count, setCount] = useState(0);

  function handleClick() {
    lastClickAt.current = Date.now();
    setCount(value => value + 1);
  }

  return <button onClick={handleClick}>Нажатий: {count}</button>;
}

Число на кнопке хранится в state, потому что его изменение должно отобразиться. Время последнего нажатия хранится в ref как служебная информация. В этом примере оно не выводится. Если нужно показывать время в интерфейсе, перенесите его в состояние или другую подходящую модель отображения.

Обычная локальная переменная создаётся заново при каждом вызове функции компонента. Ref сохраняет своё поле между рендерами. Переменная вне компонента, в свою очередь, может оказаться общей для нескольких экземпляров. Выбор ref позволяет хранить отдельное служебное значение у каждого экземпляра.

Когда можно читать и менять current

Чтение DOM и вызов focus() выполняйте в обработчике события или подходящем эффекте. Не делайте это прямо во время рендера. React должен иметь возможность вычислять интерфейс без таких побочных действий. Аналогично не используйте изменение ref во время рендера как скрытый счётчик вызовов компонента.

В Strict Mode разработческая проверка может вызвать функцию компонента дважды, причём один из созданных ref-объектов будет отброшен. Это не означает два пользовательских нажатия. Проверять обработчик нужно реальным действием, а не числом вызовов тела компонента.

Как проверить пример в приложении

  1. Откройте компонент и нажмите кнопку: каретка должна оказаться в поле.
  2. Перейдите к кнопке клавишей Tab и активируйте её с клавиатуры.
  3. Убедитесь, что фокус виден и форма не отправляется.
  4. Для условно скрываемого input проверьте действие после его удаления: код не должен обращаться к отсутствующему узлу.

Для дальнейшей практики подойдут курсы React. Если ref появился как обход сложного состояния, полезно разобрать компонент с ментором по React.

Похожие статьи по данной теме