I'm building out a component library and I'm in the process of handling state between my App.tsx
(where the component will be imported) and the component ContactActionSheet.tsx
. When the user presses the Show Modal button, the component should appear (visible
prop is used to keep track of that).
However, my component uses a modal from the react-native-modal
library and keeps track of it's visibility state with isVisible
.
My Issue: How can I keep a shared state between these two components so that I can click the Show Modal button, the modal appears. Then when I click something on the component and the emailCall function is called, the modal closes?? I'm implementing this as a library, so trying to do this without a library if possible.
App.tsx
// Imports: Dependencies
import React, { useState, useEffect } from 'react';
import { Button, SafeAreaView } from 'react-native';
// Imports: Components
import ContactActionSheet from './src/ContactActionSheet';
// React Native App
const App = () => {
// React Hooks: State
const [ visible, toggle ] = useState(false);
// Open Action Sheet
const openActionSheet = () => {
try {
// React Hook: Toggle Modal
toggle((visible: boolean) => !visible);
}
catch (error) {
console.log(error);
}
};
// Contacts
const contacts = [
{
title: 'Company Headquarters',
type: 'Phone Number',
contact: '(555) 555-5555',
},
{
title: 'Retail Store',
type: 'Phone Number',
contact: '(777) 777-7777',
},
{
title: 'Company Headquarters',
type: 'Email',
contact: 'hq@company.com',
},
{
title: 'Retail Store',
type: 'Email',
contact: 'store@company.com',
},
];
return (
<SafeAreaView>
<Button
title="Show Modal"
onPress={() => openActionSheet()}
/>
<ContactActionSheet
visible={visible}
contactsList={contacts}
/>
</SafeAreaView>
)
};
// Exports
export default App;
ContactActionSheet.tsx
// Imports: Dependencies
import React, { useState } from 'react';
import { Button, Dimensions, StyleSheet, Text, View, Linking, TouchableOpacity } from 'react-native';
import Modal from 'react-native-modal';
import { ifIphoneX } from 'react-native-iphone-x-helper';
import Icon from 'react-native-vector-icons/Ionicons';
Icon.loadFont();
// Screen Dimensions
const { height, width } = Dimensions.get('window');
// TypeScript: Types
interface Contact {
title: string;
type: 'Email' | 'Phone Number' | string;
contact: string;
}
interface Props {
visible: any;
contactsList: Array<Contact>;
}
// Component: Contact Action Sheet
const ContactActionSheet = (props: Props) => {
const [ modalVisible, toggle ] = useState(true);
// Toggle Modal
const toggleModal = () => {
try {
// Toggle
toggle((modalVisible: boolean) => !modalVisible);
}
catch (error) {
console.log(error);
}
};
// Render Modal
const renderModal = () => {
try {
if (
props.visible === true
&& modalVisible === true
) {
return true;
}
else {
return false;
}
}
catch (error) {
console.log(error);
}
};
// Render Contact Selectors
const renderContactSelectors = (props: Props) => {
try {
if (props.contactsList.length >= 6) {
console.warn('Error: Maximum of 6 contacts allowed.');
}
else {
// Map Contacts List To Contact Selector
return props.contactsList.map((contact: Contact, index: number) => {
// Render Single Contact List
if (props.contactsList.length === 1) {
return (
<TouchableOpacity key={index} style={styles.contactSelectorSingle} onPress={() => callEmail(contact)}>
<Icon name={contact.type === 'Email' ? 'ios-mail': 'ios-call'} size={28} style={styles.icon} color="#323232"></Icon>
<View>
<Text style={styles.contactTitle}>{contact.title}</Text>
<Text style={styles.emailPhone} numberOfLines={1}>{contact.contact}</Text>
</View>
</TouchableOpacity>
);
};
// Render First Index
if (props.contactsList.indexOf(contact) === 0) {
return (
<TouchableOpacity key={index} style={styles.contactSelectorFirst} onPress={() => callEmail(contact)}>
<Icon name={contact.type === 'Email' ? 'ios-mail': 'ios-call'} size={28} style={styles.icon} color="#323232"></Icon>
<View>
<Text style={styles.contactTitle}>{contact.title}</Text>
<Text style={styles.emailPhone} numberOfLines={1}>{contact.contact}</Text>
</View>
</TouchableOpacity>
);
};
// Render Middle Indexes
if (
props.contactsList.indexOf(contact) >= 1
&& props.contactsList.indexOf(contact) !== props.contactsList.length - 1
&& props.contactsList.length >= 3
) {
return (
<TouchableOpacity key={index} style={styles.contactSelector} onPress={() => callEmail(contact)}>
<Icon name={contact.type === 'Email' ? 'ios-mail': 'ios-call'} size={28} style={styles.icon} color="#323232"></Icon>
<View>
<Text style={styles.contactTitle}>{contact.title}</Text>
<Text style={styles.emailPhone} numberOfLines={1}>{contact.contact}</Text>
</View>
</TouchableOpacity>
);
};
// Render Last Index
if (props.contactsList.indexOf(contact) === props.contactsList.length - 1) {
return (
<TouchableOpacity key={index} style={styles.contactSelectorLast} onPress={() => callEmail(contact)}>
<Icon name={contact.type === 'Email' ? 'ios-mail': 'ios-call'} size={28} style={styles.icon} color="#323232"></Icon>
<View>
<Text style={styles.contactTitle}>{contact.title}</Text>
<Text style={styles.emailPhone} numberOfLines={1}>{contact.contact}</Text>
</View>
</TouchableOpacity>
);
}
});
}
}
catch (error) {
console.log(error)
}
};
// Call/Email
const callEmail = (contact: Contact) => {
try {
// Check If Email
if (contact.type === 'Email') {
// Email Details
let email = `${contact.contact}`;
let subject = `${contact.title}`;
let body = '';
// Send Email
Linking.openURL(`mailto:${email}?subject=${subject}&body=${body}`);
}
// Check If Phone Number
else if (contact.type === 'Phone Number') {
// Call Phone Number
Linking.openURL(`tel:${contact.contact}`);
}
// Toggle Modal
toggleModal();
}
catch (error) {
console.log(error);
}
};
return (
<View style={styles.container}>
<Modal
isVisible={renderModal()}
style={styles.modal}
backdropOpacity={.30}
>
<View style={styles.modalContainer}>
<View style={styles.contactListContainer}>
{renderContactSelectors(props)}
</View>
<TouchableOpacity onPress={() => toggleModal()} style={styles.cancelButtonContainer}>
<Text style={styles.cancelText}>Cancel</Text>
</TouchableOpacity>
</View>
</Modal>
</View>
);
}
// Styles
const styles = StyleSheet.create({
container: {
width: width,
},
modal: {
margin: 0,
},
modalContainer: {
height: '100%',
alignItems: 'center',
justifyContent: 'flex-end',
},
contactListContainer: {
width: width - 20,
marginBottom: 10,
alignItems: 'center',
},
contactSelectorSingle: {
width: width - 20,
height: 65,
backgroundColor: '#FFFFFF',
borderRadius: 12,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
contactSelectorFirst: {
width: width - 20,
height: 65,
backgroundColor: '#FFFFFF',
borderColor: '#7D7D7D',
borderBottomWidth: StyleSheet.hairlineWidth,
borderTopLeftRadius: 12,
borderTopRightRadius: 12,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
contactSelector: {
width: width - 20,
height: 65,
backgroundColor: '#FFFFFF',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
borderColor: '#7D7D7D',
borderBottomWidth: StyleSheet.hairlineWidth,
},
contactSelectorLast: {
width: width - 20,
height: 65,
backgroundColor: '#FFFFFF',
borderBottomLeftRadius: 12,
borderBottomRightRadius: 12,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
icon: {
marginLeft: 25,
marginRight: 25,
},
contactTitle: {
fontFamily: 'System',
fontSize: 17,
fontWeight: '500',
marginBottom: 4,
color: '#323232',
width: width - 20 - 20 - 60,
},
emailPhone: {
fontFamily: 'System',
fontSize: 15,
fontWeight: '400',
color: '#7D7D7D',
width: width - 20 - 20 - 50,
},
cancelButtonContainer: {
alignItems: 'center',
justifyContent: 'center',
width: width - 20,
height: 60,
backgroundColor: '#FFFFFF',
...ifIphoneX({
marginBottom: 35,
},
{
marginBottom: 10,
}),
borderRadius: 12,
},
cancelText: {
fontFamily: 'System',
fontSize: 20,
color: '#007AFF',
fontWeight: '600',
},
actionSheetContainer: {
borderWidth: 2,
borderColor: 'green',
backgroundColor: 'red',
},
});
// Exports
export default ContactActionSheet;
You can use a function as a react component argument :
<ContactActionSheet
visible={visible}
toggle={toggle} //your toggle setState function
contactsList={contacts}
/>
Then, in your ContactActionSheet
component, you can just do props.toggle()
which will fire the toggle
function on App
, change the state for App
, and have the desired impact on your modal (because visible
is shared)
Thank you for your help! Is
props.toggle()
coming from the React Hook state or through the props?In
ContactActionSheet
,props.toggle()
the propstoggle
received fromApp
, which is, inApp
the argument from the useStateconst [ visible, toggle ] = useState(false);
. Not sure of your question though :/