Signed-off-by: Lance Chant <13349722+lancechant@users.noreply.github.com> Co-authored-by: sarendsen <coding-mosses0z@icloud.com> Co-authored-by: Lance Chant <13349722+lancechant@users.noreply.github.com> Co-authored-by: Gauvain <68083474+Gauvino@users.noreply.github.com>
5.8 KiB
Global Modal System with Gorhom Bottom Sheet
This guide explains how to use the global modal system implemented in this project.
Overview
The global modal system allows you to trigger a bottom sheet modal from anywhere in your app programmatically, and render any component inside it.
Architecture
The system consists of three main parts:
- GlobalModalProvider (
providers/GlobalModalProvider.tsx) - Context provider that manages modal state - GlobalModal (
components/GlobalModal.tsx) - The actual modal component rendered at root level - useGlobalModal hook - Hook to interact with the modal from anywhere
Setup (Already Configured)
The system is already integrated into your app:
// In app/_layout.tsx
<BottomSheetModalProvider>
<GlobalModalProvider>
{/* Your app content */}
<GlobalModal />
</GlobalModalProvider>
</BottomSheetModalProvider>
Usage
Basic Usage
import { useGlobalModal } from "@/providers/GlobalModalProvider";
import { View, Text } from "react-native";
function MyComponent() {
const { showModal, hideModal } = useGlobalModal();
const handleOpenModal = () => {
showModal(
<View className='p-6'>
<Text className='text-white text-2xl'>Hello from Modal!</Text>
</View>
);
};
return (
<Button onPress={handleOpenModal} title="Open Modal" />
);
}
With Custom Options
const handleOpenModal = () => {
showModal(
<YourCustomComponent />,
{
snapPoints: ["25%", "50%", "90%"], // Custom snap points
enablePanDownToClose: true, // Allow swipe to close
backgroundStyle: { // Custom background
backgroundColor: "#000000",
},
}
);
};
Programmatic Control
// Open modal
showModal(<Content />);
// Close modal from within the modal content
function ModalContent() {
const { hideModal } = useGlobalModal();
return (
<View>
<Button onPress={hideModal} title="Close" />
</View>
);
}
// Close modal from outside
hideModal();
In Event Handlers or Functions
function useApiCall() {
const { showModal } = useGlobalModal();
const fetchData = async () => {
try {
const result = await api.fetch();
// Show success modal
showModal(
<SuccessMessage data={result} />
);
} catch (error) {
// Show error modal
showModal(
<ErrorMessage error={error} />
);
}
};
return fetchData;
}
API Reference
useGlobalModal()
Returns an object with the following properties:
-
showModal(content, options?)- Show the modal with given contentcontent: ReactNode- Any React component or element to renderoptions?: ModalOptions- Optional configuration object
-
hideModal()- Programmatically hide the modal -
isVisible: boolean- Current visibility state of the modal
ModalOptions
interface ModalOptions {
enableDynamicSizing?: boolean; // Auto-size based on content (default: true)
snapPoints?: (string | number)[]; // Fixed snap points (e.g., ["50%", "90%"])
enablePanDownToClose?: boolean; // Allow swipe down to close (default: true)
backgroundStyle?: object; // Custom background styles
handleIndicatorStyle?: object; // Custom handle indicator styles
}
Examples
See components/ExampleGlobalModalUsage.tsx for comprehensive examples including:
- Simple content modal
- Modal with custom snap points
- Complex component in modal
- Success/error modals triggered from functions
Default Styling
The modal uses these default styles (can be overridden via options):
{
enableDynamicSizing: true,
enablePanDownToClose: true,
backgroundStyle: {
backgroundColor: "#171717", // Dark background
},
handleIndicatorStyle: {
backgroundColor: "white",
},
}
Best Practices
- Keep content in separate components - Don't inline large JSX in
showModal()calls - Use the hook in custom hooks - Create specialized hooks like
useShowSuccessModal()for reusable modal patterns - Handle cleanup - The modal automatically clears content when closed
- Avoid nesting - Don't show modals from within modals
- Consider UX - Only use for important, contextual information that requires user attention
Using with PlatformDropdown
When using PlatformDropdown with option groups, avoid setting a title on the OptionGroup if you're already passing a title prop to PlatformDropdown. This prevents nested menu behavior on iOS where users have to click through an extra layer.
// Good - No title in option group (title is on PlatformDropdown)
const optionGroups: OptionGroup[] = [
{
options: items.map((item) => ({
type: "radio",
label: item.name,
value: item,
selected: item.id === selected?.id,
onPress: () => onChange(item),
})),
},
];
<PlatformDropdown
groups={optionGroups}
title="Select Item" // Title here
// ...
/>
// Bad - Causes nested menu on iOS
const optionGroups: OptionGroup[] = [
{
title: "Items", // This creates a nested Picker on iOS
options: items.map((item) => ({
type: "radio",
label: item.name,
value: item,
selected: item.id === selected?.id,
onPress: () => onChange(item),
})),
},
];
Troubleshooting
Modal doesn't appear
- Ensure
GlobalModalProvideris above the component callinguseGlobalModal() - Check that
BottomSheetModalProvideris present in the tree - Verify
GlobalModalcomponent is rendered
Content is cut off
- Use
enableDynamicSizing: truefor auto-sizing - Or specify appropriate
snapPoints
Modal won't close
- Ensure
enablePanDownToCloseistrue - Check that backdrop is clickable
- Use
hideModal()for programmatic closing