useUncontrolledState для неконтролируемого

useUncontrolledState — это хук, предоставляемый библиотекой React Aria, который упрощает работу с компонентами, поддерживающими как контролируемое, так и неконтролируемое состояние. Он позволяет компоненту корректно работать с внешним управлением через пропсы, а при их отсутствии автоматически использовать внутреннее состояние.


Основные концепции

В React существует различие между контролируемыми и неконтролируемыми компонентами:

  • Контролируемый компонент — состояние полностью управляется извне через пропсы value и колбэк onChange.
  • Неконтролируемый компонент — компонент сам хранит и изменяет своё состояние, обычно через useState.

useUncontrolledState объединяет эти два подхода, позволяя компоненту автоматически переключаться между ними без дублирования кода.


Сигнатура и параметры

Хук принимает объект с следующими ключами:

const [state, setState] = useUncontrolledState({
  defaultValue: initialDefaultValue,
  value: controlledValue,
  onChange: handleChange,
});
  • defaultValue — начальное значение для неконтролируемого состояния.
  • value — значение, если компонент контролируемый. Если этот проп отсутствует, используется внутреннее состояние.
  • onChange — функция обратного вызова при изменении состояния. Вызывается в обоих случаях: и при контролируемом, и при неконтролируемом использовании.

Хук возвращает массив из двух элементов:

  1. state — текущее значение.
  2. setState — функция для обновления состояния.

Пример использования

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

import { useUncontrolledState } from "@react-aria/utils";
import { useState } from "react";

function Counter() {
  const [count, setCount] = useUncontrolledState({
    defaultValue: 0
  });

  return (
    
{count}
); }

В этом примере компонент сам управляет своим состоянием. defaultValue задаёт начальное значение.

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

function ControlledCounter({ value, onChange }) {
  const [count, setCount] = useUncontrolledState({
    value,
    onChange
  });

  return (
    
{count}
); }

Если передан проп value, хук не создаёт внутреннего состояния и использует переданное значение. Любое изменение через setCount вызывает onChange, позволяя родителю контролировать состояние.


Важные особенности

  1. Синхронизация состояния useUncontrolledState автоматически синхронизирует внутреннее состояние с пропсами, если они меняются. Это особенно важно, если родитель изменяет value динамически.

  2. Обработка undefined Если value не определён, хук гарантирует, что внутреннее состояние корректно инициализируется defaultValue.

  3. Идиоматический React Использование useUncontrolledState позволяет писать чистые компоненты без проверки контролируемости вручную через условные конструкции.

  4. Поддержка любых типов данных Хук может работать с числами, строками, булевыми значениями и объектами. Главное — корректно использовать onChange для обновления состояния.


Паттерн использования в комплексных компонентах

Часто useUncontrolledState применяется в компонентах типа:

  • Select / ComboBox — управление выбранным элементом.
  • Toggle / Switch — включение/выключение.
  • Slider / Range — позиция ползунка.

Пример для Toggle:

function Toggle({ isSelected, onChange, defaultSelected }) {
  const [selected, setSelected] = useUncontrolledState({
    value: isSelected,
    defaultValue: defaultSelected,
    onChange
  });

  return (
    
  );
}

Хук делает компонент универсальным: его можно использовать как контролируемый через isSelected/onChange, так и неконтролируемый через defaultSelected.


Рекомендации по применению

  • Всегда использовать defaultValue или аналог для неконтролируемого режима. Это предотвращает ошибки с undefined.
  • Передавать onChange даже для неконтролируемых компонентов, если нужно уведомление внешних слушателей.
  • Не смешивать внутреннее и внешнее управление напрямую. Использовать только useUncontrolledState для корректного разделения ответственности.
  • Для сложных объектов использовать колбэк внутри setState, чтобы избежать проблем с мутированием состояния:
setState(prev => ({ ...prev, key: newValue }));

Итоговый паттерн

useUncontrolledState обеспечивает:

  • Универсальность компонента.
  • Чистый код без лишних проверок if (props.value !== undefined).
  • Поддержку стандартного React-паттерна контролируемых и неконтролируемых компонентов.
  • Легкую интеграцию с другими хуками React Aria.