Хук useControllableState из библиотеки Chakra
UI используется для создания компонентов, которые могут
работать одновременно в двух режимах управления состоянием:
Это распространённый паттерн проектирования UI-компонентов в экосистеме React. Он позволяет компоненту либо управлять своим состоянием самостоятельно, либо принимать значение состояния извне через props.
В Chakra UI этот хук используется внутри многих компонентов
библиотеки: например, в элементах управления вроде Switch,
Tabs, Accordion, Slider и других
интерактивных компонентов.
Основная задача useControllableState —
объединить controlled и uncontrolled поведение в одном
API, избавляя разработчика от необходимости вручную писать
однотипную логику синхронизации.
В React существуют два способа управления состоянием компонента.
Контролируемый компонент получает значение через props и сообщает об изменениях через callback.
const [value, setValue] = useState("A")
<Tabs value={value} onCha nge={setValue} />
Особенности:
Компонент управляет состоянием внутри себя, используя начальное значение.
<Tabs defaultValue="A" />
Особенности:
defaultValueПри создании UI-библиотек часто требуется поддерживать оба режима одновременно.
Пример API:
<Slider value={50} onCha nge={setValue} />
или
<Slider defaultValue={50} />
Без специальных инструментов разработчик должен самостоятельно писать проверку:
valuedefaultValueonChangeЭта логика быстро становится громоздкой и повторяется во многих компонентах.
useControllableState решает эту проблему.
useControllableStateconst [value, setValue] = useControllableState(options)
Параметры передаются через объект конфигурации.
Основные параметры:
| параметр | описание |
|---|---|
value |
контролируемое значение |
defaultValue |
начальное значение для uncontrolled режима |
onChange |
обработчик изменения состояния |
shouldUpdate |
функция проверки необходимости обновления |
import { useControllableState } from "@chakra-ui/react"
function Counter(props) {
const [count, setCount] = useControllableState({
value: props.value,
defaultValue: 0,
onChange: props.onChange,
})
return (
<button onCl ick={() => setCount(count + 1)}>
{count}
</button>
)
}
Теперь компонент поддерживает два режима.
Контролируемый:
const [value, setValue] = useState(10)
<Counter value={value} onCha nge={setValue} />
Неконтролируемый:
<Counter defaultValue={10} />
Внутри useControllableState реализована следующая
стратегия.
Проверяется, передан ли value.
Если value существует — компонент считается
controlled.
Если value отсутствует — используется
внутренний state.
При изменении состояния:
onChangeСхема:
value !== undefined
│
├── controlled → используется props.value
│
└── uncontrolled → используется useState(defaultValue)
Рассмотрим упрощённую реализацию переключателя.
function Toggle(props) {
const [isOn, setIsOn] = useControllableState({
value: props.isOn,
defaultValue: false,
onChange: props.onChange
})
const toggle = () => {
setIsOn(!isOn)
}
return (
<button onCl ick={toggle}>
{isOn ? "ON" : "OFF"}
</button>
)
}
Использование в controlled режиме:
function App() {
const [on, setOn] = useState(false)
return (
<Toggle
isOn={on}
onCha nge={setOn}
/>
)
}
Uncontrolled режим:
<Toggle defaultValue={true} />
shouldUpdateИногда необходимо предотвратить обновление состояния при определённых условиях.
Для этого используется параметр shouldUpdate.
const [value, setValue] = useControllableState({
value: props.value,
defaultValue: 0,
onChange: props.onChange,
shouldUpdate: (prev, next) => prev !== next
})
Функция получает:
prev — предыдущее значениеnext — новое значениеЕсли функция возвращает false, обновление не
происходит.
useStateuseControllableState можно рассматривать как
расширение useState, которое добавляет
поддержку controlled компонентов.
Обычный useState:
const [value, setValue] = useState(0)
useControllableState:
const [value, setValue] = useControllableState({
value,
defaultValue: 0,
onChange
})
Главное отличие:
| характеристика | useState | useControllableState |
|---|---|---|
| controlled режим | нет | да |
| uncontrolled режим | да | да |
| синхронизация props | нет | да |
| вызов onChange | вручную | автоматически |
setValueФункция setValue работает аналогично
setState:
setValue(newValue)
или
setValue(prev => prev + 1)
Однако при controlled режиме:
onChangeТаким образом источник истины остаётся у родителя.
useControllableState поддерживает обновление через
функцию.
setValue(prev => prev + 1)
Это особенно важно при асинхронных обновлениях и при работе с несколькими вызовами состояния.
Хук активно применяется при разработке:
1. Интерактивных контролов
2. Полей ввода
Компоненты формы часто должны поддерживать controlled и uncontrolled режим.
3. Компонентов состояния UI
Например:
function AccordionItem(props) {
const [isOpen, setIsOpen] = useControllableState({
value: props.isOpen,
defaultValue: false,
onChange: props.onChange
})
return (
<div>
<button onCl ick={() => setIsOpen(!isOpen)}>
Toggle
</button>
{isOpen && <div>{props.children}</div>}
</div>
)
}
Использование controlled режима:
<AccordionItem
isOpen={open}
onCha nge={setOpen}
/>
defaultValuedefaultValue используется только при первом
рендере.
Если компонент uncontrolled, значение сохраняется во внутреннем состоянии.
Изменение defaultValue после монтирования не
влияет на state.
Это поведение полностью соответствует useState.
Распространённая ошибка — одновременное использование
value и defaultValue.
<MyComponent
value={10}
defaultValue={5}
/>
В этом случае:
value имеет приоритетdefaultValue игнорируетсяКомпонент становится controlled.
useControllableState помогает формировать стандартный
API.
Типичный интерфейс компонента:
value
defaultValue
onChange
Этот паттерн используется во многих библиотеках:
Он делает компоненты предсказуемыми и гибкими.
Пример приближённой реализации:
function useControllableState({
value,
defaultValue,
onChange
}) {
const [state, setState] = useState(defaultValue)
const isControlled = value !== undefined
const currentValue = isControlled ? value : state
const setValue = (next) => {
const nextValue =
typeof next === "function"
? next(currentValue)
: next
if (!isControlled) {
setState(nextValue)
}
onChange?.(nextValue)
}
return [currentValue, setValue]
}
Настоящая реализация Chakra UI содержит дополнительные оптимизации:
Универсальность
Один компонент работает в двух режимах.
Чистый API
Пользователь компонента получает знакомый интерфейс.
Повторное использование
Логика controlled/uncontrolled не дублируется.
Предсказуемость
Поведение компонентов соответствует стандартам React.
useControllableStateХук оправдан при разработке:
Для обычных компонентов приложения чаще достаточно
useState.
useControllableState часто используется вместе с другими
утилитами Chakra:
useDisclosureuseBooleanuseCallbackRefuseMergeRefsОни формируют инфраструктуру управления состоянием в компонентах Chakra UI.
useControllableState является частью
низкоуровневого слоя управления состоянием Chakra
UI.
Он обеспечивает:
Через этот хук построено большое количество компонентов библиотеки, что делает его одним из ключевых инструментов при разработке сложных UI-элементов на основе Chakra UI.