Troubleshooting
Common issues and their solutions when using @codeleap/portals.
Portal Not Rendering
Problem: Portal opens but nothing is displayed
Cause: Missing or incorrect wrapper component configuration.
Solution:
import { Modal } from '@codeleap/portals'
import { MyModalWrapper } from './components/MyModalWrapper'
// Make sure you set the wrapper component
Modal.WrapperComponent = MyModalWrapper
Problem: Content is not showing
Cause: Forgot to call .content() method.
Solution:
// ❌ Wrong - no content defined
const MyModal = modal()
MyModal.open()
// ✅ Correct - content defined
const MyModal = modal()
.content(() => <div>Content</div>)
MyModal.open()
Problem: Portal.GlobalOutlet not rendering portals
Cause: GlobalOutlet not added to app root, or portals are independent.
Solution:
// Make sure GlobalOutlet is in your app
function App() {
return (
<div>
<YourContent />
<Portal.GlobalOutlet /> {/* Must be present */}
</div>
)
}
// Check if portal is independent
const MyModal = modal({
independent: false // Should be false for GlobalOutlet
})
TypeScript Errors
Problem: Type error on .content() props
Cause: Missing generic type parameters.
Solution:
// ❌ Wrong - no types
const MyModal = modal()
.content((props) => {
const { userId } = props // Type error
return <div>{userId}</div>
})
// ✅ Correct - with types
interface MyParams {
userId: string
}
const MyModal = modal<MyParams>()
.content((props) => {
const { userId } = props // Typed correctly
return <div>{userId}</div>
})
Problem: Type error on .request() result
Cause: Missing result type parameter.
Solution:
// ❌ Wrong
const result = await MyModal.request({ id: '123' })
result.saved // Type error
// ✅ Correct
interface MyResult {
saved: boolean
}
const MyModal = modal<MyParams, MyResult>()
const result = await MyModal.request({ id: '123' })
result.saved // Typed correctly
Problem: Type incompatibility with wrapper props
Cause: Missing WrapperProps type parameter.
Solution:
interface MyWrapperProps {
title: string
theme: 'light' | 'dark'
}
const MyModal = modal<Params, Result, Metadata, RefType, MyWrapperProps>()
.props({
title: 'My Modal',
theme: 'dark' // Now typed correctly
})
Request/Response Issues
Problem: Request promise never resolves
Cause: Forgot to call request.resolve() or request.reject().
Solution:
const MyModal = modal()
.content((props) => {
const { request } = props
// ❌ Wrong - no resolve/reject
return <button onClick={() => props.close()}>Close</button>
// ✅ Correct - resolve before close
return (
<button onClick={() => {
request?.resolve({ result: 'data' })
// close is called automatically after resolve
}}>
Submit
</button>
)
})
Problem: Can't create multiple requests
Cause: Previous request still pending.
Solution:
// Force new request
const result = await MyModal.request({ id: '123' }, true)
// Or check if request is pending
if (!MyModal.hasPendingRequest) {
const result = await MyModal.request({ id: '123' })
}
Problem: Request is null in content
Cause: Portal opened with .open() instead of .request().
Solution:
// request prop is only available when using .request()
const MyModal = modal()
.content((props) => {
if (!props.request) {
return <div>Opened normally</div>
}
return (
<div>
<button onClick={() => props.request.resolve(true)}>
Confirm
</button>
</div>
)
})
// Use .request() to get request handlers
const result = await MyModal.request()
// Using .open() won't have request prop
MyModal.open()
Hook Issues
Problem: useState hook not updating
Cause: Portal instance changed or not subscribed.
Solution:
// Make sure you're using the same portal instance
const MyModal = modal() // Create once
function Component1() {
const { visible } = MyModal.useState() // ✅ Same instance
return <div>{visible ? 'Open' : 'Closed'}</div>
}
function Component2() {
const { visible } = MyModal.useState() // ✅ Same instance
return <button onClick={MyModal.toggle}>Toggle</button>
}
Problem: useProps not updating wrapper
Cause: Missing dependencies array.
Solution:
function MyComponent({ theme, title }) {
// ❌ Wrong - no deps, won't update
MyModal.useProps({ theme, title })
// ✅ Correct - with deps
MyModal.useProps({ theme, title }, [theme, title])
return <button onClick={MyModal.open}>Open</button>
}
BottomSheet Issues
Problem: Native methods not being called
Cause: Missing key method configuration.
Solution:
import { BottomSheet } from '@codeleap/portals'
// Configure which ref methods to call
BottomSheet.openKeyMethod = 'snapToIndex'
BottomSheet.closeKeyMethod = 'close'
// Now .open() and .close() will call these methods on ref
Problem: Ref is null or undefined
Cause: Wrapper component not forwarding ref.
Solution:
import { forwardRef } from 'react'
// Wrapper must forward ref
const BottomSheetWrapper = forwardRef((props, ref) => {
return (
<BottomSheetComponent ref={ref}>
{props.children}
</BottomSheetComponent>
)
})
BottomSheet.WrapperComponent = BottomSheetWrapper
Alert Issues
Problem: Alert.modal is undefined
Cause: Forgot to assign alert modal.
Solution:
import { Alert, modal } from '@codeleap/portals'
// Must create and assign the modal
Alert.modal = modal({ id: 'ALERT' })
.content((props) => {
const { title, body, type, options } = props
return (
<div>
<h2>{title}</h2>
<p>{body}</p>
{/* Render options */}
</div>
)
})
// Now you can use alert methods
alert.error({ title: 'Error', body: 'Message' })
Debug Checklist
- WrapperComponent is configured
- GlobalOutlet is rendered
- .content() is called
- Portal is not independent (unless intended)
- TypeScript types are defined
- Request is resolved/rejected
- Transition duration matches CSS
- Dependencies are specified in hooks
- Portal instance is stable (not recreated)
- Ref is forwarded in wrapper component