First I define this component to reuse with modal windows
Template 1:
...
import Modal from 'react-native-modal';
class CustomModal extends Component {
constructor(props){
super(props);
}
componentWillReceiveProps(nextProps){
this.setState({
visible: nextProps.isVisible
})
}
state = {
visible: false
}
render() {
return (
<TouchableWithoutFeedback
onPress={()=>{
this.setState({
visible: false
});
}}
>
<Modal
isVisible={this.state.visible}
>
<TouchableWithoutFeedback onPress={() => {}}>
<View>
{this.props.content}
</View>
</TouchableWithoutFeedback>
</Modal>
</TouchableWithoutFeedback>
);
}
}
Now in a second component I call it:
import Modal from './common/modal';
return (<Modal
isVisible={this.state.showModal}
content={this.renderMyContent()}
/>
)
Clicking in a button I setState showModal : true, my modal is open, I can click outside the modal and actually the modal disappear but my state.showModal still being : true, how can I update the state in this view?
You can pass through a callback to the onModalHide prop of react-native-modal.
In your CustomModal:
<Modal onModalHide={this.props.onModalHide} ... />
In your "second template"
<Modal onModalHide={() => this.setState({ showModal: false })} ... />.
Note also that there's no need to track visibility within your CustomModal's state, since it already has its visibility in a prop. Just pass the prop down to the inner Modal directly: <Modal isVisible={this.props.isVisible} .../>
Call this.setState({showModal: false}) also in React we refer to these as components not "templates"
Create a function hideModal in the class where you are calling <Modal /> like this:
hideModal = () => {
this.setState({showModal: false})
}
and pass it to the <Modal /> as a prop follows:
import Modal from './common/modal';
return (<Modal
isVisible={this.state.showModal}
content={this.renderMyContent()}
hideModal={this.hideModal}
/>
)
And for closing call the hideModal prop as passed above from your CustomModal component as follows:
...
import Modal from 'react-native-modal';
class CustomModal extends Component {
constructor(props){
super(props);
}
componentWillReceiveProps(nextProps){
this.setState({
visible: nextProps.isVisible
})
}
state = {
visible: false
}
render() {
return (
<TouchableWithoutFeedback
onPress={()=>{
this.props.hideModal()
}}
>
<Modal
isVisible={this.state.visible}
>
<TouchableWithoutFeedback onPress={() => {}}>
<View>
{this.props.content}
</View>
</TouchableWithoutFeedback>
</Modal>
</TouchableWithoutFeedback>
);
}
}
Related
I want to control of 'Opening and Closing Modals' without using Redux. If I use Redux, i create a variable as isVisible and control it with setting a value as true or false. But, If I dont use Redux, i cant solve problem that explained below.
It's the code that I call modal function.
<TouchableOpacity onPress={ () => this.setState({ visibleModal: true ])}>
<Text> Press me! </Text> </TouchableOpacity>
<ProfileModals isVisible={this.state.visibleModal} />
Main Modal function:
export default class ProfileModals extends Component {
state = {
visibleModal: this.props.isVisible,
modal: "changeName"
};
render() {
const {visibleModal} = this.state;
return (
<Modal
isVisible={this.state.visibleModal}
backdropColor="black"
backdropOpacity={0.5}
animationOut="fadeOut"
onBackdropPress={() => this.setState({ visibleModal: false })}
swipeDirection={"down"}
onSwipeComplete={() => this.setState({ visibleModal: false })}
>
<View>
<Text>Hello!</Text>
</View>
</Modal>
);
}
}
When I press the button at the first time, Modal run correctly. After closing the modal, I press the button second time, It doesn't run anymore. Because of this.state.visibleModal value (in ProfileModal Component) is false, not this.props.isVisible.
How can i solve this problem?
dont store the modal's visibility in the state of the modal component, allow it to flow through props from the parent, follow this approach:
class Parent extends Component {
state = {
modalVisible: false,
};
openModal = () => this.setState({ modalVisible: true });
closeModal = () => this.setState({ modalVisible: false });
render() {
const { modalVisible } = this.state;
return (
<OtherStuff>
<Button onPress={this.openModal} />
<MyModal
visible={modalVisible}
closeModal={this.closeModal}
/>
</OtherStuff>
);
}
}
class MyModal extends Component {
render() {
const { visible, closeModal } = this.props;
return (
<Modal isVisible={visible}>
<Button onPress={closeModal} />
</Modal>
);
}
}
I want to create a Modal component and I want to have possibility to inject to it everything what I want. Firstly I decided to use HOC but then I've changed my solution to render props. Everything works fine but I don't really like this solution. I'm wondering how could I optimize it to make it better. What is the best practise to create such kind of modal where you have button opening this modal beyond modal component. I really don't like that now I have two components with open/close state of modal. And both of them have a toggle method to open/close modal. Any suggestion? Maybe I should stick with the HOC ?
Here's the code with Component.js where CustomModal is used:
toggleModalVisibility = (visible) => {
this.setState({modalVisible: visible});
};
render() {
const question = this.props.questions[this.props.counter];
return (
<View style={styles.questionContainer}>
<CustomModal
visible={this.state.modalVisible}
toggleModalVisibility={this.toggleModalVisibility}
render={() => (
<>
<Text>{question.text}</Text>
<Text>{question.details}</Text>
</>
)}
/>
<View style={styles.questionTextContainer}>
<Text style={styles.questionText}>{question.text}</Text>
<TouchableOpacity onPress={() => this.toggleModalVisibility(!this.state.modalVisible) }>
<FontAwesome5 name='question-circle' size={30} color='#B7DBF3' light />
</TouchableOpacity>
</View>
</View>
)
}
and here's the code of CustomModal.js
export default class CustomModal extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: this.props.visible
};
}
componentDidUpdate(prevProps) {
if (prevProps.visible !== this.props.visible) {
this.setState({isOpen: this.props.visible});
}
}
toggle = (isOpen) => {
this.setState({ isOpen });
this.props.toggleModalVisibility(isOpen)
}
render() {
return (
<View>
<Modal
animationType='slide'
transparent={false}
visible={this.state.isOpen}
>
<View style={{marginTop: 30, marginLeft: 5}}>
<TouchableHighlight
onPress={() => {
this.toggle(!this.state.isOpen)
}}>
<FontAwesome5 name='times-circle' size={30} light />
</TouchableHighlight>
<View>{this.props.render()}</View>
</View>
</Modal>
</View>
)
}
}
i want to pass a value from activity screen to modal screen. I am trying to open a screen when click on flatlist item an dpass value of item to modal,but it shows my detail before rendering modal screen,Here is my activity screen:-
<FlatList
data={this.state.myListDataSource}
renderItem={this._renderItem}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
keyExtractor={({item,index}) => item+index}
refreshControl={
<RefreshControl
refreshing={this.state.isRefreshing}
onRefresh={this.pullToRefresh}
/>
}
/>
<ListProducts
modalVisibility={this.state.listModalVisibility}
closeModal={() =>
this.setState({listModalVisibility:false})}
listName={this.state.listName}
listId={this.state.listId}
/>
handleListItemPress = (item) => {
this.setState({
listModalVisibility:true,
listName : item.name,
listId : item.list_id
})
showMessage('List Item : '+item.listId)
}
_renderItem = ({item}) => {
return(
<TouchableOpacity onPress={() => this.handleListItemPress(item)}>
<View >
<View>
<View style={{flexDirection:'row',marginBottom:2}}>
<ImageView
image={item.pictures[0]}
style={[{marginRight:2},styles.imageStyle]}
/>
<ImageView
image={item.pictures[1]}
style={[{marginLeft:2},styles.imageStyle]}
/>
</View>
<View style={{flexDirection:'row',marginTop:2}}>
<ImageView
style={[{marginRight:2},styles.imageStyle]}
image={item.pictures[2]}
/>
<ImageView
image={item.pictures[3]}
style={[{marginLeft:2},styles.imageStyle]}
/>
</View>
</View>
<View>
<TextViewNonClickable
textViewText={item.name}
/>
<TextViewNonClickable
textViewText={item.description}
/>
</View>
<Icon
name = 'more-vertical'
type = 'feather'
color = {color.colorWhite}
iconStyle={{padding:8}}
containerStyle = {{position:'absolute',top:8,right:8}}
onPress = {() => {
showMessage('Options')
}}
/>
</View>
</TouchableOpacity>
)}
This is my modal screen where i want to get list item id or name.But that screen shows details on the screen rather than rendering modal screen .
Here is my modal screen :-
export default class ListProducts extends Component {
constructor(props){
super(props)
this.state={
products : [],
refreshing : false,
listId : 256,
listName : props.name
}
}
_renderProducts = ({item}) => {
return(
<Product
image={item.image}
name={item.name}
price={item.price}
discountedPrice={item.discounted_price}
quantityAdded={item.quantity_added}
productId={item.product_id}
stock={item.stock}
/>
)
}
render() {
const {modalVisibility,closeModal,listName,listId} = this.props;
return (
<Modal
animationIn='bounceInRight'
animationOut='bounceOutRight'
isVisible={modalVisibility}
onBackButtonPress={closeModal}
>
<View style={{flex:1,backgroundColor:color.colorWhite}}>
<Header
placement='left'
leftComponent={
<FlatList
data={this.state.products}
renderItem={this._renderProducts}
keyExtractor={(item,index) => item+index}
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this.pullToRefresh}
/>
}
numColumns={3}
style={{paddingLeft:2,paddingRight:2}}
/>
</View>
</Modal>
)
}
}
Step 1: pass props from modal to class.
In modal like:
this.props.setItem(“sunny”)
Step 2: Get that props in class in render method where modal initialised.
<ModalName SetItem={item => console.log(item)} \>
I'm actually using 'Dialog' from 'react-native-simple-dialogs' for popup. It works better for me than 'Modal', but i think the logic is the same.
Here is a simplified example of a edit email popup that works for me:
import React from 'react';
import { StyleSheet, View, TextInput } from 'react-native';
import { Button, Text } from 'native-base';
import { Dialog } from 'react-native-simple-dialogs';
export default class EditEmailPopup extends React.Component {
constructor(props) {
super(props);
this.state = {
isModalVisible: this.props.isModalVisible,
};
}
componentWillUpdate() {
this.state.isModalVisible = this.props.isModalVisible;
}
_updateEmailAndCloseDialog() {
// update some data...
this._onCloseDialog();
}
_onCloseDialog() {
this.setState({ isModalVisible: false});
this.props.client(); //this is a function transfered from parent that controls the visibility of the dialog.
}
render() {
return (
<View>
<Dialog
visible={this.state.isModalVisible}
onTouchOutside={() => this._onCloseDialog()}
>
<View>
<Text style={styles.text}>{'Update Email text'}</Text>
<View style={styles.popupButtons}>
<Button
transparent
style={styles.cancelButton}
onPress={() => this._onCloseDialog()}
>
<Text> {'cancel'} </Text>
</Button>
<Button
style={styles.okButton}
onPress={() => this._updateEmailAndCloseDialog()}
>
<Text> {'ok'} </Text>
</Button>
</View>
</View>
</Dialog>
</View>
);
}
}
Here is how i add my Dialog in the parent view:
{this.state.emailModalVisibility ? (
<EditEmailPopup
isModalVisible={this.state.emailModalVisibility}
client={this.afterClosePopup}
/>
) : null}
while this.state.emailModalVisibility initiated in the constructor in state as 'false'.
function written in parent:
_afterClosePopup = () => {
this.setState({
emailModalVisibility: false
});
};
and binded in the constructor so 'this' will belong to parent:
constructor(props) {
super(props);
this.afterClosePopup = this._afterClosePopup.bind(this);
this.state = {
emailModalVisibility: false
};
}
I have a case where a feature contains a FlatList full of information, a search bar, sort button, and filter button.
For the sort and filter buttons I need to pull up a modal from the bottom that takes up half the screen.
I understand that React Navigation wants us to only create one 'root' navigator and all other navigators be dependents; however, in this particular case I would very much like to explicitly add a navigator to this page where a user presses on the filter button, brings up the modal, presses a filter option and then have the modal navigate to another filter subpage within the confines of its view, while maintaining the main page content and root navigation state in the background.
I remember implementing this in React Navigation V1.x, but does anyone know how to get around this in V2.x?
Rather than doing it with nested stack navigator and things, I've implemented your requirement using built-in react native modal.
App
import React, { Component } from 'react';
import { createStackNavigator } from 'react-navigation';
import { MainScreen } from './src/screens/MainScreen';
const RootStack = createStackNavigator(
{
MainScreen
},
{
navigationOptions: {
header: null
}
}
);
export default class App extends Component {
render() {
return (
<RootStack />
);
}
}
MainScreen
import { default as React, PureComponent } from 'react';
import { View, Text, Button, Alert, Modal } from 'react-native';
interface Props {
}
interface States {
num: number;
isFilterOneVisible: boolean;
isFilterTwoVisible: boolean;
}
export class MainScreen extends PureComponent<Props, States> {
state = {
num: 0,
isFilterOneVisible: false,
isFilterTwoVisible: false
}
render() {
return (
<View
flex={1}
justifyContent={'space-evenly'}
alignItems={'center'}
>
<Text style={{ fontSize: 50 }}>{this.state.num}</Text>
<Button
title={'CHANGE STATE'}
onPress={() => {
this.setState((prevState: States) => ({
num: prevState.num + 1
}));
}}
/>
{/* Search */}
<Button
title={'Search'}
onPress={() => {
Alert.alert('Search', 'Search clicked');
}}
/>
{/* Sort*/}
<Button
title={'Sort'}
onPress={() => {
Alert.alert('Sort Clicked', 'Sort clicked')
}}
/>
<Button
title={'Filter'}
onPress={() => {
this.setState({
isFilterOneVisible: true
})
}}
/>
{/* Filter Modal 1*/}
<Modal
visible={this.state.isFilterOneVisible}
transparent={true}
animationType={'slide'}
onRequestClose={() => {
this.setState({
isFilterOneVisible: false
})
}}
>
<View
flex={1}
justifyContent={'flex-end'}
backgroundColor={'rgba(0,0,0,0.2)'}
>
{/* Bottom */}
<View
justifyContent={'center'}
alignItems={'center'}
backgroundColor={'white'}
height={200}
>
<Button
title={'GO TO NEXT FILTER STATE'}
onPress={() => {
this.setState({
isFilterOneVisible: false,
isFilterTwoVisible: true
})
}}
/>
</View>
</View>
</Modal>
{/* Filter Modal Two */}
<Modal
visible={this.state.isFilterTwoVisible}
transparent={true}
animationType={'slide'}
onRequestClose={() => {
this.setState({
isFilterTwoVisible: false
})
}}
>
<View
flex={1}
justifyContent={'flex-end'}
backgroundColor={'rgba(0,0,0,0.2)'}
>
{/* Bottom */}
<View
justifyContent={'center'}
alignItems={'center'}
backgroundColor={'white'}
height={200}
>
<Button
title={'SET DATA AS 1000'}
onPress={() => {
this.setState({
isFilterTwoVisible: false,
num: 1000
})
}}
/>
</View>
</View>
</Modal>
</ View >
);
}
}
NOTE: The code is not optimised and follows some bad patterns like arrow-methods-in-jsx. This is just a suggestion with a working example. Feel free to enhance the code and follow the divide-and-conquer strategy ;) . The full source code can be found from here
I have implemented a View component containing a FlatList, which renders TouchableHighlights. Also I have implemented a Modal component, which I'd like to import at various places including the component that renders the FlatList.
I have already managed to open the modal from outside (via handing over a prop for visibility, accessing it via nextProps and setting modals state value "modalVisible" to this) and closing it from inside (simply via changing it's state value "modalVisible").
BUT: I also want to pass data to the modal from each FlatLists item. An item rendered as a TouchableHighlight should open the modal onPress and the modal should contain data from the item (in the code below this would be the items id). How can I achieve passing data to the modal? I somehow can't get it to work using nextProps. This seems more to be an issue related to setting state from within a FlatLists item rather than the Modal.
Modal:
export default class ModalView extends React.Component {
constructor() {
super();
this.state = {
modalVisible: false,
id: null,
};
}
componentWillReceiveProps(nextProps) {
this.setState({
modalVisible: nextProps.modalVisible,
id: nextProps.id,
})
}
render() {
return (
<Modal
animationType="slide"
transparent={ true }
visible={ this.state.modalVisible }
onRequestClose={() => { this.props.setModalVisible(false) }}
>
<View>
<View>
<Text>{ this.state.id }</Text>
<TouchableHighlight
onPress={() => { this.props.setModalVisible(false) }}
>
<Text>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
)
}
}
FlatList rendering TouchableHighlights:
export default class RecentList extends React.Component {
constructor() {
super();
this.state = {
modalVisible: false,
id: null,
}
}
_onPressItem(id) {
this.setState({
modalVisible: true,
id: id,
});
};
_renderItem = ({item}) => {
return (
<TouchableHighlight
id={item.id}
onPress={this._onPressItem}
>
<View>
<Text>{id}</Text>
</View>
</TouchableHighlight>
)
};
render() {
let data = realm.objects('Example').filtered('completed = true')
.sorted('startedAt', true).slice(0, 10)
return (
<View>
<ModalView
modalVisible={ this.state.modalVisible }
setModalVisible={ (vis) => { this.setModalVisible(vis) }}
id={ this.state.id }
/>
<FlatList
data={data}
renderItem={this._renderItem}
keyExtractor={(item, index) => index}
/>
</View>
)
}
}
A small mistake you have missed ...
_renderItem = ({item}) => {
return (
<TouchableHighlight
id={item.id}
onPress={() => this._onPressItem(item.id)} // Your not sending the item.id
>
<View>
<Text>{id}</Text>
</View>
</TouchableHighlight>
)
};