Skip to main content

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