Skip to main content

Advanced Features

Advanced patterns and techniques for power users.

Dynamic Props Based on Parameters

Props can be calculated dynamically based on the current parameters:

import { modal } from '@codeleap/portals'

const DynamicModal = modal()
.props((params) => ({
// Title changes based on parameters
title: `Editing ${params.itemName}`,

// Style varies based on importance
style: params.isImportant
? ['modal', 'urgent']
: ['modal'],

// Conditional close button
showClose: params.allowClose !== false,

// Dynamic theme
theme: params.theme || 'light',

// Size based on content
size: params.hasLongContent ? 'large' : 'medium'
}))
.content((props) => {
const { itemName, isImportant, hasLongContent } = props

return (
<div>
<p>Item: {itemName}</p>
{isImportant && <p className="warning">⚠️ Important!</p>}
{hasLongContent && <div className="long-content">...</div>}
</div>
)
})

// Usage
DynamicModal.open({
itemName: 'Product X',
isImportant: true,
allowClose: false,
hasLongContent: true,
theme: 'dark'
})

Async Initial Parameters

Load data asynchronously before the portal appears:

import { modal } from '@codeleap/portals'

const UserProfileModal = modal({
// Load user data from API
initialParams: async () => {
const userData = await fetch('/api/user').then(r => r.json())
const permissions = await fetch('/api/permissions').then(r => r.json())

return {
user: userData,
canEdit: permissions.edit,
roles: permissions.roles
}
},

// Conditionally open based on async check
startsOpen: async () => {
const shouldShow = await fetch('/api/should-show-onboarding').then(r => r.json())
return shouldShow.display
}
})
.content((props) => {
const { user, canEdit, roles } = props

return (
<div>
<h2>Welcome, {user.name}!</h2>
<p>Role: {roles.join(', ')}</p>
{canEdit && <button>Edit Profile</button>}
</div>
)
})

// The portal will load data before displaying
UserProfileModal.open()

Common Use Cases

  • Load user data before showing profile editor
  • Check permissions before opening admin panel
  • Fetch form options from API
  • Validate server conditions
  • Load dynamic translations
const EditModal = modal({
initialParams: async () => {
// Parallel data loading
const [userData, formOptions, categories] = await Promise.all([
fetch('/api/user/current').then(r => r.json()),
fetch('/api/form-options').then(r => r.json()),
fetch('/api/categories').then(r => r.json())
])

return {
user: userData,
options: formOptions,
availableCategories: categories,
timestamp: Date.now()
}
}
})

Independent Portals

Portals that don't participate in the global stack system:

import { modal } from '@codeleap/portals'

const IndependentModal = modal({
// Doesn't participate in stack management
independent: true,

// Always rendered (useful for animations)
rendersWhenHidden: true
})
.content((props) => {
const { visible } = props

return (
<div className={`special-modal ${visible ? 'visible' : 'hidden'}`}>
<p>I'm independent!</p>
</div>
)
})

// Must be rendered manually in your component tree
function App() {
return (
<div>
<YourAppContent />

{/* Independent portal rendered directly */}
<IndependentModal.Component />

{/* Other portals use the global outlet */}
<Portal.GlobalOutlet />
</div>
)
}

When to Use Independent Portals

  • Custom positioning outside the normal stack
  • Special animations or transitions
  • Always-visible components (like toast notifications)
  • Integration with external libraries
  • Complex z-index management

Lifecycle Hooks

Execute code when portals open or close:

import { modal } from '@codeleap/portals'

const TrackedModal = modal({ id: 'TRACKED' })
.onOpen((portal) => {
console.log('Modal opened!')
console.log('Portal ID:', portal.id)
console.log('Current params:', portal.getParams())
console.log('Stack position:', portal.stackIndex)

// Track analytics
analytics.track('modal_opened', {
modal_id: portal.id,
params: portal.getParams()
})

// Focus first input
setTimeout(() => {
portal.ref.current?.querySelector('input')?.focus()
}, 100)
})
.onClose((portal) => {
console.log('Modal closed!')

// Track analytics
analytics.track('modal_closed', {
modal_id: portal.id,
duration: Date.now() - portal.getParams().openedAt
})

// Clean up
portal.resetParams()
})
.content((props) => {
return <div>Modal with lifecycle tracking</div>
})

BottomSheet Native Methods

For BottomSheet, lifecycle hooks can control native ref methods:

import { bottomSheet, BottomSheet } from '@codeleap/portals'

// Configure which ref methods to call
BottomSheet.openKeyMethod = 'snapToIndex'
BottomSheet.closeKeyMethod = 'close'

const NativeSheet = bottomSheet()
.onOpen((portal) => {
// Called after native open method
console.log('Sheet opened via ref method')
console.log('Ref:', portal.ref.current)
})
.onClose((portal) => {
// Called after native close method
console.log('Sheet closed via ref method')
})
.content(() => <div>Bottom Sheet</div>)

Accessing Portals from Anywhere

Using Portal Registry

import { Portal, Modal } from '@codeleap/portals'

// Create portal with ID
const UserModal = modal({ id: 'USER_MODAL' })
.content(() => <div>User modal</div>)

// Access from anywhere in your app
function SomeOtherComponent() {
const openUserModal = () => {
const portal = Portal.registry.getInstance('USER_MODAL')
portal?.open({ userId: '123' })
}

return <button onClick={openUserModal}>Open User Modal</button>
}

// Or use Modal-specific registry
function AnotherComponent() {
const portal = Modal.registry.getInstance('USER_MODAL')

return (
<div>
<p>Modal is {portal?.isVisible ? 'open' : 'closed'}</p>
<button onClick={() => portal?.toggle()}>Toggle</button>
</div>
)
}

Centralized Portal Management

// portals.js
import { modal, drawer, bottomSheet } from '@codeleap/portals'

export const Portals = {
User: modal({ id: 'USER' }),
Settings: drawer({ id: 'SETTINGS' }),
Filters: bottomSheet({ id: 'FILTERS' }),
Confirm: modal({ id: 'CONFIRM' }),
Alert: modal({ id: 'ALERT' })
}

// Define content
Portals.User.content(() => <UserModalContent />)
Portals.Settings.content(() => <SettingsDrawerContent />)
// ... etc

// Use anywhere
import { Portals } from './portals'

function App() {
return (
<button onClick={() => Portals.User.open({ userId: '123' })}>
Open User Modal
</button>
)
}

Conditional Rendering

Control when portal content should render:

const ConditionalModal = modal({
// Render even when hidden (for exit animations)
rendersWhenHidden: true
})
.content((props) => {
const { visible, data } = props

// Don't render if no data
if (!data) return null

return (
<div className={visible ? 'fade-in' : 'fade-out'}>
{data.content}
</div>
)
})

Reset Parameters Control

Control whether parameters reset when closing:

// Always reset (default)
const ResetModal = modal({
resetParamsOnClose: true
})

// Preserve parameters
const PreserveModal = modal({
resetParamsOnClose: false
})
.content((props) => {
const { counter = 0 } = props

return (
<div>
<p>Counter: {counter}</p>
<button onClick={() => props.setParams({ counter: counter + 1 })}>
Increment
</button>
</div>
)
})

// Counter persists between opens
PreserveModal.open()
// User increments to 5
PreserveModal.close()
PreserveModal.open() // Counter is still 5

Custom Transition Durations

Configure animation timings:

// Global default for all modals
import { Modal } from '@codeleap/portals'
Modal.DEFAULT_TRANSITION_DURATION = 400

// Per-instance
const SlowModal = modal({
transitionDuration: 800
})

const FastModal = modal({
transitionDuration: 150
})

Working with Refs

Access wrapper component refs:

import { bottomSheet } from '@codeleap/portals'

const SheetWithRef = bottomSheet()
.content((props) => {
const { ref } = props

const snapToTop = () => {
ref.current?.snapToIndex?.(0)
}

const expand = () => {
ref.current?.expand?.()
}

return (
<div>
<button onClick={snapToTop}>Snap to Top</button>
<button onClick={expand}>Expand</button>
</div>
)
})

// Or access from outside
SheetWithRef.open()
setTimeout(() => {
SheetWithRef.ref.current?.snapToIndex?.(1)
}, 1000)

Stacking and Z-Index

Understanding portal stacking:

import { modal } from '@codeleap/portals'

const FirstModal = modal({ id: 'FIRST' })
.content((props) => {
const { stackIndex } = props
return <div>Stack index: {stackIndex}</div>
})

const SecondModal = modal({ id: 'SECOND' })
.content((props) => {
return <div>Stack index: {props.stackIndex}</div>
})

// Open multiple modals
FirstModal.open() // stackIndex: 0
SecondModal.open() // stackIndex: 1

// Access stack information
console.log(FirstModal.stackIndex) // 0
console.log(SecondModal.stackIndex) // 1

// Close SecondModal - FirstModal stack index stays 0
SecondModal.close()

Filtering Stack

import { Portal } from '@codeleap/portals'

// Get all open portals
const openPortals = Portal.registry.filter(p => p.isVisible)
console.log('Open portals:', openPortals.length)

// Get all portals with pending requests
const waitingPortals = Portal.registry.filter(p => p.hasPendingRequest)

// Get all independent portals
const independentPortals = Portal.registry.filter(p => p._config.independent)

Metadata

Store additional configuration data:

const CategorizedModal = modal({
id: 'CATEGORIZED',
metadata: {
category: 'user-management',
requiresAuth: true,
analytics: {
trackOpens: true,
eventName: 'user_modal_opened'
}
}
})

// Access metadata
console.log(CategorizedModal._config.metadata)

// Use in hooks
CategorizedModal.onOpen((portal) => {
const { metadata } = portal._config

if (metadata.analytics.trackOpens) {
analytics.track(metadata.analytics.eventName)
}

if (metadata.requiresAuth && !isAuthenticated()) {
portal.close()
redirectToLogin()
}
})