apps/docs/content/docs/react/migration/hooks.mdx
HeroUI v3 removes most component hooks that existed in v2, replacing them with compound components and a new hook for overlay state management. This guide covers:
HeroUI v2 provided component hooks (like useSwitch, useInput, useCheckbox, etc.) that returned prop getters (getBaseProps, getWrapperProps, getThumbProps, etc.) to customize component structure when users couldn't directly modify inner child components. HeroUI v3 solves this with compound components, eliminating the need for hooks.
In v2, components had fixed internal structures. To customize these structures, users needed to use hooks that provided prop getters. For example, useSwitch returned getBaseProps(), getWrapperProps(), getThumbProps(), etc., which users could spread onto custom elements to build their own Switch structure.
v3 uses compound component patterns that give you direct access to component parts. Instead of using hooks with prop getters, you compose components directly using subcomponents like Switch.Control, Switch.Thumb, Checkbox.Control, Checkbox.Indicator, etc.
@heroui/react that include hook names (useSwitch, useInput, useCheckbox, useRadio, etc.)useSwitch to create a switch without a thumb, don't add Switch.Thumb in v3useCheckbox to create a checkbox without an indicator, don't add Checkbox.Indicator in v3v2: Switch without thumb
import { useSwitch } from "@heroui/react";
function CustomSwitch() {
const { getBaseProps } = useSwitch();
return (
<div {...getBaseProps()}>
</div>
);
}
v3: Equivalent structure
import { Switch } from "@heroui/react";
function CustomSwitch() {
return (
<Switch.Control>
</Switch.Control>
);
}
For detailed migration examples for specific components, see the individual component migration guides.
The useDisclosure hook from v2 has been replaced with useOverlayState in v3. This hook manages open/close state for modals, popovers, and other overlay components.
API:
const {isOpen, onOpen, onClose, onOpenChange, isControlled, getButtonProps, getDisclosureProps} = useDisclosure({
isOpen?: boolean;
defaultOpen?: boolean;
onClose?(): void;
onOpen?(): void;
onChange?(isOpen: boolean | undefined): void;
id?: string;
});
Example:
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button, useDisclosure } from "@heroui/react";
export default function App() {
const {isOpen, onOpen, onOpenChange} = useDisclosure();
return (
<>
<Button onPress={onOpen}>Open Modal</Button>
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent>
<ModalHeader>Title</ModalHeader>
<ModalBody>Content</ModalBody>
<ModalFooter>
<Button onPress={onOpenChange}>Close</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
}
API:
const state = useOverlayState({
isOpen?: boolean;
defaultOpen?: boolean;
onOpenChange?: (isOpen: boolean) => void;
});
// Returns:
// {
// isOpen: boolean;
// open(): void;
// close(): void;
// toggle(): void;
// setOpen(isOpen: boolean): void;
// }
Example:
import { Modal, Button, useOverlayState } from "@heroui/react";
export default function App() {
const state = useOverlayState();
return (
<Modal>
<Button onPress={state.open}>Open Modal</Button>
<Modal.Container isOpen={state.isOpen} onOpenChange={state.setOpen}>
<Modal.Dialog>
{({close}) => (
<>
<Modal.Header>
<Modal.Heading>Title</Modal.Heading>
</Modal.Header>
<Modal.Body>Content</Modal.Body>
<Modal.Footer>
<Button onPress={close}>Close</Button>
</Modal.Footer>
</>
)}
</Modal.Dialog>
</Modal.Container>
</Modal>
);
}
v2:
const {isOpen, onOpen, onClose, onOpenChange} = useDisclosure();
v3:
const state = useOverlayState();
// Use state.open(), state.close(), state.toggle(), state.setOpen(boolean)
v2:
const {isOpen, onOpenChange} = useDisclosure({
isOpen: controlledIsOpen,
onChange: (isOpen) => setControlledIsOpen(isOpen)
});
v3:
const state = useOverlayState({
isOpen: controlledIsOpen,
onOpenChange: setControlledIsOpen
});
v2:
const {isOpen, onOpen, onClose} = useDisclosure({
defaultOpen: false
});
v3:
const state = useOverlayState({
defaultOpen: false
});
// Use state.open(), state.close(), state.toggle()
| v2 (useDisclosure) | v3 (useOverlayState) | Notes |
|---|---|---|
isOpen | isOpen | Same |
onOpen() | open() | Renamed method |
onClose() | close() | Renamed method |
onOpenChange() | toggle() | New method for toggling |
onOpenChange (prop) | setOpen(boolean) | Different API |
isControlled | - | Removed (handled internally) |
getButtonProps() | - | Removed (use compound components) |
getDisclosureProps() | - | Removed (use compound components) |
open(), close(), toggle()) instead of callbacksFor simple cases, you can also use React's useState directly:
import { useState } from "react";
import { Modal, Button } from "@heroui/react";
export default function App() {
const [isOpen, setIsOpen] = useState(false);
return (
<Modal>
<Button onPress={() => setIsOpen(true)}>Open</Button>
<Modal.Container isOpen={isOpen} onOpenChange={setIsOpen}>
<Modal.Dialog>
</Modal.Dialog>
</Modal.Container>
</Modal>
);
}
However, useOverlayState provides a cleaner API with dedicated methods for common operations.
The following hooks from v2 have been removed in v3:
useSwitch, useInput, etc.) → Use compound components insteadopen(), close(), toggle(), and setOpen() methodsuseDraggable, useClipboard, usePagination, useToast are no longer availableuseState can be used directly, but useOverlayState offers better ergonomicsFor component-specific hook migration examples, refer to the individual component migration guides.