Skip to main content

Portal-based API

The Portal API offers a declarative way to define bottom sheets. You declare the sheet in your component tree and control it imperatively via useBottomSheetControl.

Key Features

  • Context Preservation - Sheet content stays in your React tree, so it has access to all context (theme, auth, i18n, navigation)
  • Define Once, Open from Anywhere - Declare a sheet in one place, open it from any other component within the same provider
  • Declarative Definition - Sheet structure is defined in JSX, making it easy to see what sheets exist

Basic Usage

import {
BottomSheetPortal,
useBottomSheetControl,
} from 'react-native-bottom-sheet-stack';

function MyComponent() {
const { open, close } = useBottomSheetControl('my-sheet');

return (
<View>
{/* Declare the portal - content stays in your React tree */}
<BottomSheetPortal id="my-sheet">
<MySheet />
</BottomSheetPortal>

{/* Control it imperatively */}
<Button title="Open" onPress={() => open({ scaleBackground: true })} />
</View>
);
}

How It Works

The portal API uses react-native-teleport to render content in your component tree while displaying it in BottomSheetHost. This preserves all React context from the definition site.

Open from Anywhere

Define the sheet once, open it from a completely different part of your app:

// In screens/Settings.tsx - define the sheet
function SettingsScreen() {
return (
<View>
<BottomSheetPortal id="language-picker">
<LanguagePickerSheet />
</BottomSheetPortal>

{/* ... settings UI */}
</View>
);
}

// In components/Header.tsx - open from anywhere
function Header() {
const { open } = useBottomSheetControl('language-picker');

return (
<TouchableOpacity onPress={() => open()}>
<LanguageIcon />
</TouchableOpacity>
);
}

Both components must be rendered within the same BottomSheetManagerProvider.

Context Preservation

Your sheet content retains access to React context:

import { useTheme } from './ThemeContext';

const ThemedSheet = forwardRef((props, ref) => {
const { colors } = useTheme(); // Context is preserved!

return (
<BottomSheetManaged ref={ref} snapPoints={['50%']}>
<BottomSheetView style={{ backgroundColor: colors.background }}>
<Text style={{ color: colors.text }}>Themed Content</Text>
</BottomSheetView>
</BottomSheetManaged>
);
});

function App() {
const { open } = useBottomSheetControl('themed-sheet');

return (
<ThemeProvider>
<BottomSheetPortal id="themed-sheet">
<ThemedSheet />
</BottomSheetPortal>

<Button title="Open Themed Sheet" onPress={() => open()} />
</ThemeProvider>
);
}

Updating Params

You can update or reset params on an already open sheet using updateParams and resetParams:

import {
BottomSheetPortal,
useBottomSheetControl,
useBottomSheetStatus,
useBottomSheetContext,
} from 'react-native-bottom-sheet-stack';

const UserSheet = forwardRef((props, ref) => {
const { params } = useBottomSheetContext<'user-sheet'>();
const [user, setUser] = useState(null);

useEffect(() => {
if (params?.userId) {
fetchUser(params.userId).then(setUser);
}
}, [params?.userId]);

return (
<BottomSheetManaged ref={ref} snapPoints={['50%']}>
<BottomSheetView>
{user && <Text>{user.name}</Text>}
</BottomSheetView>
</BottomSheetManaged>
);
});

function UserList() {
const { open, updateParams, resetParams } = useBottomSheetControl('user-sheet');
const { isOpen } = useBottomSheetStatus('user-sheet');

const showUser = (userId: string) => {
if (isOpen) {
// Sheet already open - just update the params
updateParams({ userId });
} else {
// Open with initial params
open({ params: { userId } });
}
};

const clearSelection = () => {
// Reset params to undefined
resetParams();
};

return (
<View>
<BottomSheetPortal id="user-sheet">
<UserSheet />
</BottomSheetPortal>

<Button title="Show User 1" onPress={() => showUser('user-1')} />
<Button title="Show User 2" onPress={() => showUser('user-2')} />
<Button title="Clear" onPress={clearSelection} />
</View>
);
}