В библиотеке Radix UI все компоненты построены с учётом управления состоянием как изнутри (internal state), так и извне (controlled state). Контроль состояния извне позволяет интегрировать Radix-компоненты в более сложные сценарии, где состояние управляется глобально или через сторонние менеджеры состояния, такие как Redux, Zustand или Recoil.
Uncontrolled (неуправляемый) компонент хранит своё
состояние внутренне. Например, компонент Accordion сам
управляет тем, какие панели открыты.
Controlled (управляемый) компонент получает состояние извне через пропсы и уведомляет об изменениях через callback-функции. Это позволяет полностью синхронизировать состояние компонента с внешними источниками данных.
Пример различий:
// Неуправляемый Accordion
<Accordion>
<AccordionItem value="item-1">
<AccordionTrigger>Панель 1</AccordionTrigger>
<AccordionContent>Контент 1</AccordionContent>
</AccordionItem>
</Accordion>
// Управляемый Accordion
const [openItem, setOpenItem] = useState<string | null>(null);
<Accordion value={openItem} onValueCha nge={setOpenItem}>
<AccordionItem value="item-1">
<AccordionTrigger>Панель 1</AccordionTrigger>
<AccordionContent>Контент 1</AccordionContent>
</AccordionItem>
</Accordion>
Ключевые моменты:
value задаёт текущее состояние компонента.onValueChange сообщает о попытках изменения
состояния.Управляемый Accordion позволяет синхронизировать
открытые панели с глобальным состоянием:
const [openItem, setOpenItem] = useState<string | null>(null);
<Accordion type="single" value={openItem} onValueCha nge={setOpenItem}>
<AccordionItem value="item-1">
<AccordionTrigger>Панель 1</AccordionTrigger>
<AccordionContent>Контент панели 1</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>Панель 2</AccordionTrigger>
<AccordionContent>Контент панели 2</AccordionContent>
</AccordionItem>
</Accordion>
type="single" определяет возможность открытия
только одной панели одновременно.value={openItem} контролирует, какая панель
открыта.onValueCha nge={setOpenItem} синхронизирует изменения с
внешним состоянием.Компонент Tabs отлично демонстрирует управление
состоянием извне:
const [selectedTab, setSelectedTab] = useState('tab1');
<Tabs value={selectedTab} onValueCha nge={setSelectedTab}>
<TabsList>
<TabsTrigger value="tab1">Вкладка 1</TabsTrigger>
<TabsTrigger value="tab2">Вкладка 2</TabsTrigger>
</TabsList>
<TabsContent value="tab1">Контент вкладки 1</TabsContent>
<TabsContent value="tab2">Контент вкладки 2</TabsContent>
</Tabs>
value задаёт активную вкладку.onValueChange позволяет обрабатывать переключение и
синхронизировать его с внешними состояниями или эффектами.Для всех управляемых компонентов Radix важно правильно использовать props, которые отвечают за состояние:
value / defaultValue –
текущее значение или начальное состояние.
value используется для контролируемого компонента.defaultValue для неуправляемого, устанавливает
начальное состояние.onValueChange /
onOpenChange – callback для уведомления о попытке
изменения состояния.
disabled / readOnly –
позволяют блокировать управление извне, сохраняя визуальное
поведение.
Использование управляемого режима открывает возможности интеграции с Redux, Zustand, Recoil или Context API. Пример с Zustand:
import create from 'zustand';
const useStore = create(set => ({
openAccordion: null,
setOpenAccordion: (value) => set({ openAccordion: value })
}));
function AccordionWrapper() {
const { openAccordion, setOpenAccordion } = useStore();
return (
<Accordion value={openAccordion} onValueCha nge={setOpenAccordion}>
<AccordionItem value="item-1">
<AccordionTrigger>Панель 1</AccordionTrigger>
<AccordionContent>Контент панели 1</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>Панель 2</AccordionTrigger>
<AccordionContent>Контент панели 2</AccordionContent>
</AccordionItem>
</Accordion>
);
}
value и onValueChange
для синхронизации компонента с внешним состоянием, если требуется
согласованное управление.defaultValue и value в одном
компоненте — это создаёт непредсказуемое поведение.onValueChange для побочных эффектов, таких
как запросы к API, а не только для обновления локального состояния.Radix UI автоматически поддерживает анимацию раскрытия или
переключения контента в управляемом режиме. Важно только управлять
состоянием через value и onValueChange.
Анимации не ломаются при контролируемом состоянии, так как компонент
всегда знает текущее и предыдущее значение.
value и onValueChange
позволяет интегрировать компоненты в глобальные хранилища
состояния.