Portal System
The MeoNode UI Portal System provides a robust, stack-based solution for managing overlays like modals, drawers, and tooltips. It supports nested portals, state synchronization, and imperative control with full type safety.
Architecture
The system consists of three main parts:
- PortalProvider: Manages the portal stack and provides the context.
- PortalHost: The target container where portals are rendered (usually at the root).
- usePortal Hook: The primary interface for opening and closing portals.
Basic Setup
To use the portal system, you must wrap your application (or the relevant branch) with PortalProvider and include a PortalHost where you want the portals to be injected.
import { PortalProvider, PortalHost, Div } from '@meonode/ui' const Layout = ({ children }) => PortalProvider({ children: [ children, PortalHost() // Usually placed at the end to render on top ] })
Using usePortal Hook
The usePortal hook is the primary way to interact with the portal system.
import { usePortal, Button, Div, Text } from '@meonode/ui' // 1. Define your portal content component const MyModal = ({ data, close }) => Div({ padding: 24, backgroundColor: 'theme.base', children: [ Text(\`Hello, \${data.name}!\`), Button('Close', { onClick: close }) ] }) // 2. Use the hook in your component const App = () => { const portal = usePortal() return Button('Open Modal', { onClick: () => portal.open(MyModal, { name: 'World' }) }) }
Hook API
portal.open(Component, data?): Opens a new portal layer.portal.close(): Closes the current (top-most) portal layer managed by this hook instance.portal.updateData(data): Updates the data of the current portal layer without re-rendering the parent.
Auto-Sync Feature
One of the most powerful features of MeoNode's portal system is automatic data synchronization. When you pass a data object to the usePortal hook, any portal opened by that hook instance will automatically stay in sync with that data.
This means when the parent component re-renders with new state, the portal content receives the updated data automatically without having to manually call portal.updateData().
import React, { useState } from 'react' import { usePortal, Button, Div, Text, type PortalLayerProps } from '@meonode/ui' // 1. Define the type of the data object. interface CountData { count: number setCount: React.Dispatch<React.SetStateAction<number>> } // 2. Define the portal content. // It receives the synced data via the 'data' prop. const CounterModal = ({ data, close }: PortalLayerProps<CounterData>) => Div({ padding: 24, backgroundColor: 'theme.base', children: [ Text(`Current count: ${data.count}`), Button('Increment Count', { onClick: () => data.setCount(c => c + 1) }), Button('Close', { onClick: close }) ] }) const MyComponent = () => { const [count, setCount] = useState(0) // 3. Pass the state (or any object) to usePortal. // This enables auto-sync for all portals opened by this hook instance. const portal = usePortal({ count, setCount }) return Div({ children: [ Button('Open Counter Modal', { onClick: () => portal.open(CounterModal) }), ] }) }
Data Channels
For high-frequency updates or state that shouldn't trigger parent re-renders, you can use createDataChannel.
import { createDataChannel, useDataChannel } from '@meonode/ui' const countChannel = createDataChannel(0) const PortalContent = () => { const count = useDataChannel(countChannel) return Text(\`Count: \${count}\`) } // Update from anywhere countChannel.set(prev => prev + 1)
Nested Portals
Portals can open other portals. The system automatically manages the stack and depth.
const Outer = () => { const portal = usePortal() return Button('Open Inner', { onClick: () => portal.open(Inner) }) }
On this page
- Portal System