React: сфокусировать поле ввода по чекбоксу
Tags: react, hooks, useref
Published: 2022-11-10 19:36:56

_Как установить фокус на поле ввода (инпут) при изменении чекбокса?_

Даны чекбокс `<input type="checkbox">` и поле ввода `<input type="text">`. Хочу фокусировать поле ввода, когда пользователь отмечает чекбокс &mdash; так пользователю будет понятно, что от него ожидается ввод текста, если чекбокс включён.
Напишу компонент, который показывает чекбокс и поле ввода, значение для поля ввода сохраню с помощью хуков `useState` и `useCallback`:

```jsx
export const App = () => {
  const [value, setValue] = useState("");
  const handleInputChange = useCallback(
    (event) => setValue(event.target.value),
    []
  );

  return (
    <main>
      <input type="checkbox" />
      <input type="text" value={value} onChange={handleInputChange} />
    </main>
  );
};
```

Добавлю обработчик на изменение чекбокса: покажу алерт с новым значением. Обработчик события вызывается с объектом `event`, в свойстве `target` лежит элемент, который вызвал событие, в моем случае `event.target` &mdash; чекбокс. Новое значение чекбокса можно получить из его свойства `checked`: `event.target.checked`:

```jsx
export const App = () => {
  const [value, setValue] = useState("");
  const handleInputChange = useCallback(
    (event) => setValue(event.target.value),
    []
  );
  const handleChange = useCallback((event) => {
    alert(event.target.checked);
  }, []);

  return (
    <div className="App">
      <input type="checkbox" onChange={handleChange} />
      <input type="text" value={value} onChange={handleInputChange} />
    </div>
  );
};
```

Теперь, при изменении чекбокса, установлю фокус в поле ввода. У элементов, которые могут получать фокус ввода, есть метод `focus`. Чтобы вызвать этот метод у поля ввода, его нужно найти - в этом поможет механизм ref-ов и хук `useRef`.
Создам ref и передам его пропсой полю ввода:

```jsx
const inputRef = useRef();
// ...
<input 
  type="text"
  value={value}
  onChange={handleInputChange}
  ref={inputRef}
/>;

```

React, после рендера элементов, поместит в `inputRef` ссылку на поле ввода. У всех ref-ов есть свойство `current`, в котором хранится значение ref-а. Воспользуюсь им, чтобы вызвать метод `focus` у элемента, для этого в обработчик изменения чекбокса добавлю `inputRef.current.focus()`:

```jsx
export const App = () => {
  const [value, setValue] = useState("");
  const inputRef = useRef();
  const handleInputChange = useCallback((e) => setValue(e.target.value), []);
  const handleChange = useCallback(
    (e) => {
      if (e.target.checked) {
        inputRef.current.focus();
      }
    },
    [inputRef]
  );
  return (
    <div className="App">
      <input type="checkbox" onChange={handleChange} />
      <input
        type="text"
        value={value}
        onChange={handleInputChange}
        ref={inputRef}
      />
    </div>
  );
};
```

Иногда может оказаться, что решение с использованием ref-ов слишком сложное. Тогда можно попробовать найти нужный элемент с помощью DOM API &mdash; например, `document.querySelector`. Чтобы найти элемент используя `querySelector`, его нужно как-то идентифицировать, добавлю ему `id`. Затем, в обработчике изменения чекбокса, заменю ref на вызов `document.querySelector`:

```jsx
export const App = () => {
  const [value, setValue] = useState("");
  const handleInputChange = useCallback((e) => setValue(e.target.value), []);
  const handleChange = useCallback((e) => {
    if (e.target.checked) {
      document.querySelector("#input").focus();
    }
  }, []);
  return (
    <div className="App">
      <input type="checkbox" onChange={handleChange} />
      <input
        id="input"
        type="text"
        value={value}
        onChange={handleInputChange}
      />
    </div>
  );
};
```

Использовать `documenent.querySelector` нежелательно:
 * нет гарантии, что найдет нужный элемент &mdash; сколько еще может быть на странице элементов с таким же селектором? 
 * компонент перестанет работать в окружении, где нет `document`.
 * querySelector будет искать элемент по всему дереву, а React сохранит ссылку на элемент в ref-е при рендере, что быстрее.

Поэтому вариант с ref-ами более стабильный и предсказуемый.