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

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

Основная концепция

В React различают контролируемые и неконтролируемые компоненты:

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

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

import { useControlledState } from '@react-stately/utils';

function Example({ value, defaultValue, onChange }) {
  const [state, setState] = useControlledState(value, defaultValue, onChange);

  return (
    <input
      value={state}
      onCha nge={e => setState(e.target.value)}
    />
  );
}

Параметры хука

Хук useControlledState принимает три параметра:

  1. value — текущее значение, если компонент контролируемый.
  2. defaultValue — начальное значение, если компонент неконтролируемый.
  3. onChange — колбэк, который вызывается при изменении значения.

Этот подход гарантирует, что:

  • Если передан value, внутреннее состояние полностью синхронизировано с пропсом и любое изменение инициируется через onChange.
  • Если value не передан, хук создает внутреннее состояние и управляет им самостоятельно.

Возвращаемое значение

useControlledState возвращает массив [state, setState]:

  • state — текущее значение состояния.
  • setState — функция для обновления состояния. Если компонент контролируемый, она вызывает onChange, если неконтролируемый — обновляет внутреннее состояние напрямую.

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

Контролируемый компонент
function ControlledInput({ value, onChange }) {
  const [state, setState] = useControlledState(value, undefined, onChange);

  return (
    <input
      value={state}
      onCha nge={e => setState(e.target.value)}
    />
  );
}

// Использование
<ControlledInput value={externalValue} onCha nge={handleChange} />

В этом примере состояние компонента полностью контролируется внешним value. Любое изменение инициируется колбэком onChange.

Неконтролируемый компонент
function UncontrolledInput({ defaultValue }) {
  const [state, setState] = useControlledState(undefined, defaultValue);

  return (
    <input
      value={state}
      onCha nge={e => setState(e.target.value)}
    />
  );
}

// Использование
<UncontrolledInput defaultValue="Hello" />

Здесь компонент использует внутреннее состояние, которое инициализируется defaultValue.

Особенности и рекомендации

  • Синхронизация состоянияuseControlledState автоматически обновляет внутреннее состояние при изменении value, если компонент контролируемый.
  • Управление колбэками — вызов setState всегда учитывает, контролируем компонент или нет, предотвращая дублирование состояния.
  • Совместимость с React Aria — хук используется внутри многих компонентов библиотеки для унифицированного управления состоянием, включая элементы формы, селекты и чекбоксы.
  • Поддержка сложных типов — состояние может быть не только примитивным (string, number), но и объектом или массивом, при этом onChange будет получать актуальное значение.

Пример с объектным состоянием

function ControlledForm({ value, onChange }) {
  const [formData, setFormData] = useControlledState(value, { name: '', age: '' }, onChange);

  return (
    <form>
      <input
        value={formData.name}
        onCha nge={e => setFormData({ ...formData, name: e.target.value })}
      />
      <input
        value={formData.age}
        onCha nge={e => setFormData({ ...formData, age: e.target.value })}
      />
    </form>
  );
}

Такой подход обеспечивает:

  • Поддержку как контролируемых, так и неконтролируемых форм.
  • Упрощение синхронизации данных с внешними источниками.
  • Минимизацию лишних ререндеров и колбэков.

Важные моменты при интеграции

  1. Никогда не передавать одновременно value и defaultValue с различными начальными состояниями — это может вызвать несогласованность.
  2. Всегда проверять, что onChange корректно обновляет внешнее состояние, если компонент контролируемый.
  3. Использовать useControlledState предпочтительно для повторно используемых компонентов, чтобы избежать дублирования логики состояния.

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