React native create custom modal using render props - javascript

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>
)
}
}

Related

const [item1, item2] inside class component (react-native)?

I've been working on a React-Native project.
For export default function App() INSERT 1 to 3 (on code) works.
For export default class App extends Component none of the INSERT's works.
I have to combine them since the modal gives the user the ability to insert text inside the modal and then process the data to console.log and from there use the data.
export default class App extends Component {
{/* INSERT 1 before render also gives error */}
render () {
{/* INSERT 1 */}
const [list, setList] = useState();
const HandleAddList = () => {
console.log(list);
{/* INSERT 1 END */}
return (
<View>
<Modal
animationType = {"slide"}
transparent={false}
visible={this.state.isVisible}>
<View style={styles.ModalContext}>
<View style={styles.ModalNavigation}>
<Text style={[styles.closeText, styles.navText]}
onPress={() => {
this.displayModal(!this.state.isVisible);
}
}> Cancel </Text>
<Text style = {[styles.navHeader, styles.navText] }>
New</Text>
<Text style={[styles.doneText, styles.navText]}
onPress={() => {
this.displayModal(!this.state.isVisible);
{/* INSERT 2 */}
HandleAddList();
{/* INSERT 2 */}
}
}> Done </Text>
</View>
<TextInput
style={styles.inputText}
placeholder='Enter Something...'
{/* INSERT 3 */}
value = {list}
onChangeText={text => setList(text)}
{/* INSERT 3 */}
autoFocus
/>
</View>
</Modal>
{/* Rest of the code */}
</View>
{/* const stylesheets etc. */}
React-native's documentation told me that I can't use const inside a class component. (https://reactjs.org/warnings/invalid-hook-call-warning.html).
INSERT-comments were only for the purpose of the question and testing was done without it...
All the needed modules was imported from 'react-native'
Any solutions? Would be grateful if someone can help...
You can't use Hooks on Class Components, it's only a Functional Components' feature. Instead of that you could use this,I'm not pretty sure about some things but you can fix the errors:
import styles from './styles.css'
export default function App() {
const [list, setList] = useState();
const [isVisible, setIsVisible] = useState(true);
const HandleAddList = () => {
console.log(list);
}
return (
<View>
<Modal
animationType={"slide"}
transparent={false}
visible={isVisible}>
<View style={styles.ModalContext}>
<View style={styles.ModalNavigation}>
<Text style={[styles.closeText, styles.navText]}
onPress={() => {
setIsVisible(!isVisible);
}
}> Cancel </Text>
<Text style={[styles.navHeader, styles.navText]}>
New</Text>
<Text style={[styles.doneText, styles.navText]}
onPress={() => {
setIsVisible(!isVisible);
HandleAddList();
}
}> Done </Text>
</View>
<TextInput
style={styles.inputText}
placeholder='Enter Something...'
value={list}
onChangeText={text => setList(text)}
autoFocus
/>
</View>
</Modal>
</View>
)
}
I'm not used to Class Components, but I think this can guide you:
export default class App extends Component {
constructor() {
super()
this.state = {
isVisible: true,
list: ""
}
}
HandleAddList () {
console.log(this.state.list);
}
render () {
return (
<View>
<Modal
animationType={"slide"}
transparent={false}
visible={this.state.isVisible}>
<View style={styles.ModalContext}>
<View style={styles.ModalNavigation}>
<Text style={[styles.closeText, styles.navText]}
onPress={() => {
this.setState({...this.state, isVisible: !this.state.isVisible});
}
}> Cancel </Text>
<Text style={[styles.navHeader, styles.navText]}>
New</Text>
<Text style={[styles.doneText, styles.navText]}
onPress={() => {
this.setState({...this.state, isVisible: !this.state.isVisible});
this.HandleAddList();
}
}> Done </Text>
</View>
<TextInput
style={styles.inputText}
placeholder='Enter Something...'
value={this.state.list}
onChangeText={text => this.setState({ ...this.state, list: text})}
autoFocus
/>
</View>
</Modal>
</View>
)
}
}
It's so important you read the documentation (https://es.reactjs.org/docs/state-and-lifecycle.html) by yourself, there's a pair of things here you could fix reading it. It's a pleasure to help anyway, hope this works for you.

How to pass and execute functions as props in class Component in React Native?

I'm a beginner in React Native and struggling in passing and executing functions as props from parent to child component. Here's the code:
MainMap
import React from 'react';
import {
TouchableWithoutFeedback,
StyleSheet,
View,
Button,
FlatList,
Dimensions
} from 'react-native';
import PlaceInput from '../components/PlaceInput';
const INCREMENT = 1;
const HEIGHT = Dimensions.get('window').height
const WIDTH = Dimensions.get('window').width
class MainMap extends React.Component{
constructor(props){
super(props);
this.state={
numOfInput:[],
counter: 0,
}
this.onAddSearch = this.onAddSearch.bind(this)
this.onDeleteSearch = this.onDeleteSearch.bind(this)
}
onAddSearch(){
this.setState((state) => ({
counter: state.counter + INCREMENT,
numOfInput: [...state.numOfInput, state.counter]
}))
}
onDeleteSearch(inputId){
const items = this.state.numOfInput.filter(item => item.id !== inputId)
this.setState({
numOfInput: items
})
}
render(){
return(
<TouchableWithoutFeedback onPress={this.hideKeyboard} >
<View style={styles.container} >
<Button title='Add a location' onPress={this.onAddSearch} />
<View style={{height: HEIGHT/2 }}>
<FlatList
data={this.state.numOfInput}
keyExtractor={(item, index) => item.id}
renderItem={itemData => {
return(
<PlaceInput
key={itemData.item.id}
// id={itemData.item.id}
onDelete={this.onDeleteSearch}
showDirectionOnMap={this.showDirectionOnMap}
userLatitude={userLatitude}
userLongitude={userLongitude}
/>
)
}}
/>
</View>
</View>
</TouchableWithoutFeedback>
)
}
}
export default MainMap;
const styles = StyleSheet.create({
container:{
flex: 1
},
})
Here's the PlaceInput component
class PlaceInput extends React.Component{
constructor(props){
super(props);
... // These lines have no relation to what I'm asking so don't mind them
}
...
render(){
return(
<View style={styles.buttonContainer} >
<View style={{flex: 1, alignItems: 'center'}}>
<Text style={{fontSize: 8}}>{'\u25A0'}</Text>
</View>
<View style={{flex: 4}}>
<TextInput
autoCorrect={false}
autoCapitalize='none'
style={styles.inputStyle}
placeholder='Search your places'
onChangeText={(input) => {
this.setState({destinationInput: input});
this.getPlacesDebounced(input);
}}
value={this.state.destinationInput}
/>
{/* {predictions} */}
</View>
<View style={styles.rightCol}>
<TouchableOpacity onPress={this.props.onDelete.bind(this, this.props.id)}>
<Ionicons name='md-car' size={25} style={{alignSelf: 'center'}} />
</TouchableOpacity>
</View>
</View>
)
}
}
What I'm trying to do:
Define a function to execute in MainMap.js (in FlatList --> PlaceInput for specific) , which is to delete an search bar( the whole PlaceInput in the FlatList) every time I click the right symbol of that search bar. The function is onDeleteSearch
The right symbol is styled in a TouachableOpacity as you can see in the PlaceInput.js component. I put it in the last View pair
However, When I click, the screen deletes all the search bars, not the one I click. Is it the problem of the id of the component PlaceInput ? Or with the way I call the props?...
Please help me !
<TouchableOpacity onPress={this.props.onDelete.bind(this, this.props.id)}>
<Ionicons name='md-car' size={25} style={{alignSelf: 'center'}} />
</TouchableOpacity>
Don't bind, just call this.props.onDelete(this.props.id);
In MainMap, try this:
<PlaceInput
key={itemData.item.id}
// id={itemData.item.id}
onDelete={() => this.onDeleteSearch(itemData.item.id)} // here
showDirectionOnMap={this.showDirectionOnMap}
userLatitude={userLatitude}
userLongitude={userLongitude}
/>
Assuming the function:
onPressed(optionalArgument = false) {
// do something
}
You can pass a function to onPress if it does not require any arguments, i.e
onPress={onPressed} // - would work if no arguments required.
onPress={onPressed(argument)} // - will get fired on component render
onPress={()=> onPressed(argument)} // - will work as expected on button press
onPress={()=> { // - will work as expected on button press
// Multiple lines of code
onPressed(argument);
anotherFunction();
}
};
In your MainMap you are doing everything correctly, just uncomment the
// id={itemdata.item.id}
In PlaceInput, just one small change:
<TouchableOpacity onPress={() => this.props.onDelete(this.props.id)}>
<Ionicons name='md-car' size={25} style={{alignSelf: 'center'}} />
</TouchableOpacity>
If you don't add ()=> to your onPress, the function gets called immediately, that's why you see such behaviour.
Your numOfInput is just a list of numbers, so instead of using item.id-s use item directly.
Here:
const items = this.state.numOfInput.filter(item => item !== inputId)
And here
<PlaceInput
key={itemData.item}
// id={itemData.item}
...
/>

React Native Warning: Cannot update during an existing state transition (such as within `render`)

How do I get rid of this warning? I know I need to get rid of setState functions in the render method, but I need them, so where should I put them?
export default class List<T> extends React.PureComponent<ListProps<T>> {
state = { wrapped: false, iconName: "arrow-down" };
render(): React.Node {
const { rows, renderRow, title, onPress } = this.props;
if (this.state.wrapped === true) {
list = undefined;
this.setState({ iconName: "arrow-up" });
} else {
list = rows.map((row, index) => (
<View key={index} style={index !== rows.length - 1 ? styles.separator : {}}>
{renderRow(row, index)}
</View>
));
this.setState({ iconName: "arrow-down" });
}
return (
<TouchableWithoutFeedback>
<View style={styles.container}>
<View style={[styles.separator, styles.relative]}>
<Text style={styles.title}>{title}</Text>
<IconButton
style={styles.icon}
onPress={() => this.setState({ wrapped: !this.state.wrapped })}
name={this.state.iconName}
color="black"
/>
</View>
{list}
</View>
</TouchableWithoutFeedback>
);
}}
No, you don't need to get rid of setState calls in your render method in general. You just need to put them so that they are not called in each render call (by binding them to user events like clicks for example) and thereby trigger another re-render, that again calls setState and again re-renders and so on.
So in your particular case, you are firing setState right in the beginning in the if() { ... } else { ... } statements. No matter what this.state.wrapped is, you end up at setState.
Here is a possible solution for how you might want to change your code specifically to make it what I assume you want it to make:
export default class List<T> extends React.PureComponent<ListProps<T>> {
state = { wrapped: false };
render(): React.Node {
const { rows, renderRow, title, onPress } = this.props;
const { wrapped } = this.state;
return (
<TouchableWithoutFeedback>
<View style={styles.container}>
<View style={[styles.separator, styles.relative]}>
<Text style={styles.title}>{title}</Text>
<IconButton
style={styles.icon}
onPress={() => this.setState({ wrapped: !wrapped })}
name={wrapped ? "arrow-up" : "arrow-down"}
color="black"
/>
</View>
{!wrapped && (
<View key={index} style={index !== rows.length - 1 ? styles.separator : {}}>
{renderRow(row, index)}
</View>
)}
</View>
</TouchableWithoutFeedback>
);
}}
Because the value of your icon is directly correlated to wrapped, you don't need to specifically set the icon in the state. Rather infer it from wrapped.

How to pass a value to modal and show modal when click on flatlist item in react native

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
};
}

How to switch tabs from another component using react-native-scrollable-tab-view

Second day using React Native so I've no idea what I'm doing, but how do I switch tabs from within another component with react-native-scrollable-tab-view?
https://github.com/skv-headless/react-native-scrollable-tab-view
I have a MenuButton in a ButtonPage that I'm trying to switch tabs with using an onPress. However when I tap it I get:
undefined is not an object (evaluating 'this.props.tabView.goToPage')
What I've got is this
export default class Home extends Component {
render() {
return (
<View style={{flex: 1}}>
<StatusBar
barStyle='light-content'/>
<ScrollableTabView initialPage={1} renderTabBar={false}
ref={(tabView) => { this.tabView = tabView}}>
<FriendsPage tabView={this.tabView} tabLabel="Friends" />
<ButtonPage tabView={this.tabView} tabLabel="Button" />
<HangoutsPage tabView={this.tabView} tabLabel="Hangouts" />
</ScrollableTabView>
</View>
)
}
}
And in my ButtonPage I have this
export default class ButtonPage extends Component {
render() {
return (
<View style={styles.container}>
<View style={styles.buttonsContainer}>
<HangoutsButton/>
<View style={styles.spacer}/>
<MenuButton/>
</View>
</View>)
}
}
And that MenuButton is described by this
export default class MenuButton extends Component {
constructor(props) {
super(props)
this.goToPage = this.goToPage.bind(this)
}
goToPage(pageNumber) {
this.props.tabView.goToPage(pageNumber)
}
render() {
return (
<Indicator
position='left top'
value='3'
type='warning'>
<TouchableHighlight
onPress={() => this.goToPage(0)}
underlayColor='#ed8600'
style={styles.touchable}>
<Image source={require('./resources/menuicon.png')} style={styles.image} />
</TouchableHighlight>
</Indicator>
)
}
}
How do I get the reference for my scrollableTabView all the way down to my button so I can tap it and change the page with goToPage?
I assumed you could stick the object in a prop and pass that prop all the way down and then use the function goToPage on it from MenuButton.
You don't need to tabview ref all the way down. Just use
export default class MenuButton extends Component {
render() {
return (
<Indicator
position='left top'
value='3'
type='warning'>
<TouchableHighlight
onPress={() => this.props.onPress && this.props.onPress()}
underlayColor='#ed8600'
style={styles.touchable}>
<Image source={require('./resources/menuicon.png')} style={styles.image} />
</TouchableHighlight>
</Indicator>
)
}}
export default class ButtonPage extends Component {
render() {
return (
<View style={styles.container}>
<View style={styles.buttonsContainer}>
<HangoutsButton/>
<View style={styles.spacer}/>
<MenuButton onPress={ () => this.props.goToPage && this.props.goToPage() }/>
</View>
</View>)
}}
And finally
export default class Home extends Component {
goToPage(pageId) {
this.tabView.goToPage(pageId);
}
render() {
return (
<View style={{flex: 1}}>
<StatusBar
barStyle='light-content'/>
<ScrollableTabView initialPage={1} renderTabBar={false}
ref={(tabView) => { this.tabView = tabView}}>
<FriendsPage tabView={this.tabView} tabLabel="Friends" />
<ButtonPage tabView={this.tabView} tabLabel="Button" goToPage={ () => this.goToPage(1) } />
<HangoutsPage tabView={this.tabView} tabLabel="Hangouts" />
</ScrollableTabView>
</View>
)
}
}

Categories

Resources