Managing Modal State in React with Chakra UI's useDisclosure Hook
Learn how Chakra UI's useDisclosure hook simplifies modal, drawer, and popover state management with a practical example and tips for controlled usage.
23 Jul 2026, 01:03 UTC

The problem: duplicated state logic for UI overlays
When building a React app that needs several overlay components—modals, drawers, popovers—you often end up writing the same useState pattern over and over: a boolean for open/closed, plus handler functions to flip it. This boilerplate clutters components and makes it easy to forget to keep the state in sync when you add a new trigger.
Why useDisclosure helps
Chakra UI provides the useDisclosure hook, which returns a ready‑made object: isOpen, onOpen, onClose, and toggle. By importing it from @chakra-ui/react you get a single source of truth for visibility state that works out‑of‑the‑box with Chakra’s Modal, Drawer, Popover, and Tooltip components.
Worked example: a button‑controlled modal
import { Button, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalCloseButton, useDisclosure } from '@chakra-ui/react';
function DemoModal() {
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<>
Open modal
Hello from useDisclosure
This modal is controlled entirely by the hook.
);
}
Place this component anywhere in your React tree. Clicking the button calls onOpen, setting isOpen to true and rendering the Modal. Clicking the overlay or the close button invokes onClose, returning isOpen to false. No extra state is needed.
Controlled usage: letting a parent dictate the initial state
Sometimes you need the parent to decide whether the overlay should start open (e.g., based on a route param). Pass an isOpen prop to useDisclosure:
function Parent() {
const [forcedOpen, setForcedOpen] = React.useState(false);
const disclosure = useDisclosure({ isOpen: forcedOpen });
return (
<>
setForcedOpen(!forcedOpen)}>
Toggle forced state
);
}
function ChildModal({ disclosure }) {
const { isOpen, onOpen, onClose } = disclosure;
return (
<>
Open from child
Controlled modal
Its initial state comes from the parent.
);
}
The hook merges the prop with its internal togglers, so onOpen and onClose still work while the parent can override isOpen at any time.
Trade‑off and limitation
Because useDisclosure returns a single boolean, it is best suited for UI elements that share the same open/closed logic. If you try to drive unrelated components—say, a modal and a tooltip—with the same instance, you risk unwanted side effects: opening the modal will also show the tooltip. The recommended practice is to scope each hook to a logically related group of components.
Another nuance appears when you rely on CSS animations for fade‑out. Setting isOpen to false immediately unmounts the overlay, which can cut off the animation. A common workaround is to keep the component mounted while adding a temporary isAnimating flag, or to use Chakra’s Transition component to delay the state change.
How to verify it works
- Create a fresh React project (
npx create-react-app my-app) and install Chakra UI v2 (npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion). - Add the Chakra provider at the root (
<ChakraProvider>). - Paste the
DemoModalcomponent into a page and run the app (npm start). - Open the browser console and log
isOpeninside the component to see it toggle betweenfalseandtrueas you interact with the button and overlay. - For the controlled variant, render
Parentand observe that the modal’s initial state follows the parent’sforcedOpenvalue, while the child button still toggles it.
If the console shows the expected boolean changes and the overlay appears/disappears without errors, the hook is behaving as documented.
Takeaway
Use useDisclosure to centralize visibility state for any group of Chakra UI overlay components. It cuts boilerplate, keeps state in sync, and offers a clean path for both uncontrolled and controlled usage—just remember to keep the hook’s scope limited to related UI and to handle animation delays when needed.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.