Skip to content

Commit

Permalink
feat(apps): multiple modals
Browse files Browse the repository at this point in the history
  • Loading branch information
crherman7 committed Jan 15, 2025
1 parent 7404109 commit 1d0fbec
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 22 deletions.
1 change: 1 addition & 0 deletions apps/example-react-navigation/src/app/routes/account.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import {Text, View, StyleSheet, Button} from 'react-native';

import {useModal} from '../../shared/components/ModalProvider';

export function Account() {
Expand Down
6 changes: 1 addition & 5 deletions apps/example-react-navigation/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import React from 'react';
import {
NavigationContainer,
LinkingOptions,
useNavigation,
} from '@react-navigation/native';
import {NavigationContainer, LinkingOptions} from '@react-navigation/native';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import {createStackNavigator} from '@react-navigation/stack';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export function SignInModal(props: any) {

hideModal('blah');
});
}, [hideModal]);
}, [hideModal, props.route.params.__modalId]);

return (
<View style={styles.container}>
<Text style={styles.text}>/account/signin</Text>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,66 @@
import React, {createContext, useContext, useState, useCallback} from 'react';
import {NavigationProp, useNavigation} from '@react-navigation/native';
import React, {createContext, useContext, useCallback} from 'react';
import {
NavigationProp,
useNavigation,
useRoute,
} from '@react-navigation/native';

type ModalContextType = {
/**
* Shows a modal by navigating to the specified modal route.
* Returns a promise that resolves with a value when the modal is dismissed.
*
* @param modalName - The name of the modal route to navigate to.
* @param params - Optional parameters to pass to the modal.
* @returns A promise that resolves with the value provided when the modal is dismissed.
*/
showModal: (modalName: string, params?: object) => Promise<any>;
hideModal: (value: any) => void;

/**
* Hides the modal with the given modal ID and resolves its associated promise.
*
* @param modalId - The unique ID of the modal to hide.
* @param value - The value to resolve the modal's promise with.
*/
hideModal: (modalId: string, value: any) => void;
};

let modalIdCounter = 0; // Counter for generating unique modal IDs
const modalMap = new Map<string, (value: any) => void>(); // Map to store modal resolve functions

const ModalContext = createContext<ModalContextType | undefined>(undefined);

/**
* ModalProvider Component
* Provides `showModal` and `hideModal` functions to manage modals.
*/
export const ModalProvider: React.FC<{children: React.ReactNode}> = ({
children,
}) => {
const navigation = useNavigation<NavigationProp<any>>(); // Generic navigation prop
const [resolveFunction, setResolveFunction] = useState<
((value: any) => void) | null
>(null);
const navigation = useNavigation<NavigationProp<any>>();

const showModal = useCallback(
(modalName: string, params?: object) => {
return new Promise<any>(resolve => {
setResolveFunction(() => resolve); // Store the resolve function
navigation.navigate(modalName, params); // Use navigation to navigate to the modal
const modalId = `modal_${modalIdCounter++}`; // Generate a unique ID for the modal
modalMap.set(modalId, resolve); // Store the resolve function in the map

navigation.navigate(modalName, {...params, __modalId: modalId}); // Navigate to the modal
});
},
[navigation],
);

const hideModal = useCallback(
(value: any) => {
if (resolveFunction) {
resolveFunction(value); // Resolve the promise with the provided value
setResolveFunction(null); // Clear the resolve function
(modalId: string, value: any) => {
const resolve = modalMap.get(modalId);
if (resolve) {
resolve(value); // Resolve the promise with the provided value
modalMap.delete(modalId); // Remove the modal from the map
}
navigation.goBack(); // Dismiss the modal
navigation.goBack(); // Navigate back to dismiss the modal
},
[resolveFunction, navigation],
[navigation],
);

return (
Expand All @@ -44,10 +70,45 @@ export const ModalProvider: React.FC<{children: React.ReactNode}> = ({
);
};

export const useModal = () => {
/**
* Hook to access the modal context.
* Throws an error if used outside a `ModalProvider`.
*
* @returns The modal context with `showModal` and `hideModal` functions.
*/
export const useModalContext = (): ModalContextType => {
const context = useContext(ModalContext);
if (!context) {
throw new Error('useModal must be used within a ModalProvider');
}
return context;
};

/**
* A custom hook for managing the current modal's lifecycle.
* Automatically retrieves the `__modalId` from the navigation route params.
*
* @returns An enhanced `hideModal` function and other modal utilities.
*/
export const useModal = () => {
const {hideModal, showModal} = useModalContext();
const route = useRoute();

const currentModalId = (route.params as {__modalId?: string})?.__modalId;

const enhancedHideModal = (value: any) => {
console.log('Current Modal Id: ', currentModalId);
if (!currentModalId) {
throw new Error(
'useModal must be used within a modal with a valid `__modalId` in route.params.',
);
}

hideModal(currentModalId, value);
};

return {
hideModal: enhancedHideModal,
showModal,
};
};

0 comments on commit 1d0fbec

Please sign in to comment.