React Navigation - pop() go's back to root rather than previous page - reactjs

I am trying to add a back buttons into a bespoke sidemenu component.
Here is my navigation.js setup:
import { createAppContainer, createStackNavigator, createDrawerNavigator } from 'react-navigation';
import App from '../components/App';
import HomeView from '../components/HomeView';
import CheckIn from '../components/CheckIn';
import LoginView from '../components/LoginView';
import LrmDocs from '../components/LrmDocs';
import Rota from '../components/Rota';
import SideMenu from '../components/util/SideMenu';
const InitStack = createStackNavigator({
AppContainer: App,
LoginViewContainer:LoginView,
});
const RootStack = createDrawerNavigator({
Auth:InitStack,
HomeViewContainer: HomeView,
RotaContainer: Rota,
CheckInContainer:CheckIn,
LrmDocsContainer: LrmDocs,
},
{
drawerPosition: 'right',
contentComponent: SideMenu,
cardStyle: { backgroundColor: '#FFFFFF'},
drawerLockMode: 'locked-closed'
}
);
let Navigation = createAppContainer(RootStack);
export default Navigation;
I have the following setup in my bespoke sidemenu -
mport React, {Component} from 'react';
import CopyrightSpiel from './CopyrightSpiel';
import {ScrollView, Text, View, StyleSheet, Image, Button, TouchableOpacity} from 'react-native';
import { withNavigation } from 'react-navigation';
import { connect } from "react-redux";
import { authLogout, clearUser } from "../../store/actions/index";
class SideMenu extends Component {
constructor(props) {
super(props);
this.state = { loggedIn:false};
}
logOutHandler = () => {
this.props.onTryLogout();
this.props.clearUser();
this.props.navigation.navigate('AppContainer');
};
render() {
const isLoggedIn = () => {
if(this.state.loggedIn){
return true;
}
else {return false; }
};
let cp = this.props.activeItemKey;
let getCurrentCoordinates = (pg) => {
if(cp === pg){
return true;
}
};
return (
<View style={styles.container}>
<ScrollView>
<View style={styles.header}>
<View style={styles.closeBtnWrap}>
<TouchableOpacity
onPress={() => this.props.navigation.toggleDrawer() }
>
<Image
style={styles.closeBtnImg}
source={require('../../images/icons/ico-close.png')}
/>
</TouchableOpacity>
</View>
<View style={styles.logoBlock}>
<Image style={styles.homeBlockImg} source={require('../../images/loginLogo.png')} />
</View>
</View>
<View style={styles.navSection}>
<TouchableOpacity
onPress={() => this.props.navigation.navigate('HomeViewContainer')}
>
<View style={[styles.navSectionStyle, getCurrentCoordinates('HomeViewContainer') && styles.navItemSel]}>
<Text style={styles.navItemStyle}>
HOME
</Text>
</View>
</TouchableOpacity>
<TouchableOpacity
onPress={() => this.props.navigation.navigate('RotaContainer')}
>
<View style={[styles.navSectionStyle, getCurrentCoordinates('RotaContainer') && styles.navItemSel]}>
<Text style={styles.navItemStyle} >
MY ROTA
</Text>
</View>
</TouchableOpacity>
<TouchableOpacity
onPress={() => this.props.navigation.navigate('LrmDocsContainer')}
>
<View style={[styles.navSectionStyle, getCurrentCoordinates('LrmDocsContainer') && styles.navItemSel]}>
<Text style={styles.navItemStyle} >
LRM DOCS
</Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={this.logOutHandler}>
<View style={styles.navSectionStyle}>
<Text style={styles.navItemStyle} >
SIGN OUT
</Text>
</View>
</TouchableOpacity>
</View>
<View style={styles.navSection}>
<Text style={styles.navSectionTitle}>Current Shift Options:</Text>
<TouchableOpacity
onPress={() => this.props.navigation.navigate('CheckInContainer')}
>
<View style={[styles.navSectionStyle, isLoggedIn() && styles.checkedInHide, getCurrentCoordinates('CheckInContainer') && styles.navItemSel]}>
<Text style={styles.navItemStyle} >
CHECK IN
</Text>
</View>
</TouchableOpacity>
<TouchableOpacity
onPress={() => this.props.navigation.navigate('CheckInContainer')}
>
<View style={[styles.navSectionStyle, !(isLoggedIn()) && styles.checkedOutHide, getCurrentCoordinates('CheckInContainer') && styles.navItemSel]}>
<Text style={styles.navItemStyle} >
CHECK OUT
</Text>
</View>
</TouchableOpacity>
</View>
</ScrollView>
<View style={styles.footerContainer}>
<CopyrightSpiel color="LightGrey"/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
header:{
flexDirection:'row',
justifyContent:'center',
},
closeBtnWrap:{
position:'absolute',
left:15,
top:0,
opacity:0.8,
},
closeBtnImg:{
width:22,
height:22
},
container:{
backgroundColor:'#1C2344',
alignSelf:'stretch',
flex:1,
paddingTop:50,
borderLeftColor:'#F7931E',
borderLeftWidth:1
},
logoBlock:{
alignSelf:'center',
},
homeBlockImg:{
marginTop:10,
width:60,
height:105,
},
navSectionStyle:{
alignItems:'stretch',
textAlign: 'center',
backgroundColor:'rgba(255,255,255,0.1)',
paddingTop:8,
paddingBottom:8,
marginTop:15,
},
navItemSel:{
backgroundColor:'rgba(255,255,255,0.9)',
},
navItemSelText:{
color:'#1C2344',
},
navItemStyle:{
color:'#F7931E',
fontSize:24,
alignSelf:'center'
},
navSection:{
marginTop:30
},
navSectionTitle:{
color:'rgba(255,255,255,0.5)',
marginLeft:15,
},
footerContainer:{
paddingBottom:15,
},
checkedInHide:{
display:'none',
},
checkedOutHide:{
display:'none',
},
});
const mapDispatchToProps = dispatch => {
return {
onTryLogout: () => dispatch(authLogout()),
clearUser: () => dispatch(clearUser())
};
};
export default connect(null, mapDispatchToProps)(withNavigation(SideMenu));
which works fine -
I then have the following in my sub view header:
import React from 'react';
import {View, Image, Text, StyleSheet, TouchableOpacity, Button} from 'react-native';
import PropTypes from 'prop-types';
import { withNavigation } from 'react-navigation';
import { StackActions } from 'react-navigation';
class FixedHeader extends React.Component {
render() {
const popAction = StackActions.pop({
n: 0,
});
return (
<View style={FixedHeaderStyles.sectionHeader}>
<View style={FixedHeaderStyles.sectionHeaderTopLine}>
<View style={[FixedHeaderStyles.sectionHeaderBack, !(this.props.backButton) && FixedHeaderStyles.sectionHeaderHide ]}>
<TouchableOpacity
onPress={() => this.props.navigation.dispatch(popAction)}
>
<Text style={FixedHeaderStyles.sectionHeaderText}>< Back</Text>
</TouchableOpacity>
</View>
<View style={FixedHeaderStyles.logoBlock}>
<Image style={FixedHeaderStyles.homeBlockImg} source={require('../../images/logosmall.png')} />
</View>
<View style={FixedHeaderStyles.homeBlockBurger} >
<TouchableOpacity onPress={() => this.props.navigation.toggleDrawer() }>
<Image
style={FixedHeaderStyles.homeBurgerImg}
source={require('../../images/icons/ico-burger.png')}
/>
</ TouchableOpacity>
</View>
</View>
</View>
);
}
}
FixedHeader.propTypes = {
backButton: PropTypes.bool.isRequired,
navigate: PropTypes.object,
};
const FixedHeaderStyles = StyleSheet.create({
sectionHeadeLogo:{
width:45,
height:58,
alignSelf:'center'
},
sectionHeader:{
backgroundColor:'#1C2344',
flex:1.8,
alignSelf:'stretch',
borderBottomColor:'#f79431',
borderBottomWidth:1,
},
sectionHeaderTopLine:{
height:120,
paddingTop:45,
borderBottomColor:'#f79431',
borderBottomWidth:1,
flexDirection:'row',
justifyContent:'center',
alignItems:'center'
},
homeBlockBurger:{
position:'absolute',
right:0,
marginRight:15,
top:56
},
logoBlock:{
alignSelf:'flex-start',
},
homeBlockImg:{
width:45,
height:58,
alignSelf:'center',
},
homeBurgerImg:{
width:40,
height:40,
},
sectionHeaderHide:{
display:'none',
},
sectionHeaderBack:{
position:'absolute',
left:15,
top:70,
},
sectionHeaderText:{
color:'#fff',
},
});
export default withNavigation(FixedHeader);
The page navigates fine to the subview - but when pressing back the page jumps to the root (login page) rather than the previous page. For example - I navigate to rota from home, click back and jump back to login.. can anyone shine any light on this and point me in the correct direction - I've read the documentation but cant figure out whats going astray...
My Dependencies are as follows:

Here's how to do it in #react-navigation 6.x -we're in 2022 xD-
import { StackActions } from '#react-navigation/native';
const popAction = StackActions.pop(1);
navigation.dispatch(popAction);
documentation demo
https://reactnavigation.org/docs/stack-actions/#pop
snack demo https://snack.expo.dev/JEqNk9eBx

You shouldn't define your popAction with index to 0 in your FixedHeader class.
const popAction = StackActions.pop({
n: 0,
});
instead try
const popAction = StackActions.pop();
The pop action takes you back to a previous screen in the stack.
The n param allows you to specify how many screens to pop back
by.
Please refer to these docs: https://reactnavigation.org/docs/en/stack-actions.html#pop
Besides that, you would better define your const popAction = ... outside render() method.
import React from 'react';
import {View, Image, Text, StyleSheet, TouchableOpacity, Button} from 'react-native';
import PropTypes from 'prop-types';
import { withNavigation } from 'react-navigation';
import { StackActions } from 'react-navigation';
const popAction = StackActions.pop();
class FixedHeader extends React.Component {
render() {
return (
<View style={FixedHeaderStyles.sectionHeader}>
<View style={FixedHeaderStyles.sectionHeaderTopLine}>
<View style={[FixedHeaderStyles.sectionHeaderBack, !(this.props.backButton) && FixedHeaderStyles.sectionHeaderHide ]}>
<TouchableOpacity
onPress={() => this.props.navigation.dispatch(popAction)}
>
<Text style={FixedHeaderStyles.sectionHeaderText}>< Back</Text>
</TouchableOpacity>
</View>
<View style={FixedHeaderStyles.logoBlock}>
<Image style={FixedHeaderStyles.homeBlockImg} source={require('../../images/logosmall.png')} />
</View>
<View style={FixedHeaderStyles.homeBlockBurger} >
<TouchableOpacity onPress={() => this.props.navigation.toggleDrawer() }>
<Image
style={FixedHeaderStyles.homeBurgerImg}
source={require('../../images/icons/ico-burger.png')}
/>
</ TouchableOpacity>
</View>
</View>
</View>
);
}
}
FixedHeader.propTypes = {
backButton: PropTypes.bool.isRequired,
navigate: PropTypes.object,
};
const FixedHeaderStyles = StyleSheet.create({
sectionHeadeLogo:{
width:45,
height:58,
alignSelf:'center'
},
sectionHeader:{
backgroundColor:'#1C2344',
flex:1.8,
alignSelf:'stretch',
borderBottomColor:'#f79431',
borderBottomWidth:1,
},
sectionHeaderTopLine:{
height:120,
paddingTop:45,
borderBottomColor:'#f79431',
borderBottomWidth:1,
flexDirection:'row',
justifyContent:'center',
alignItems:'center'
},
homeBlockBurger:{
position:'absolute',
right:0,
marginRight:15,
top:56
},
logoBlock:{
alignSelf:'flex-start',
},
homeBlockImg:{
width:45,
height:58,
alignSelf:'center',
},
homeBurgerImg:{
width:40,
height:40,
},
sectionHeaderHide:{
display:'none',
},
sectionHeaderBack:{
position:'absolute',
left:15,
top:70,
},
sectionHeaderText:{
color:'#fff',
},
});
export default withNavigation(FixedHeader);
Cheers

Since you're using the withNavigation HOC, you'll want to use the navigation prop in your FixedHeader component instead of calling StackActions.pop. you can just call this.props.navigation.pop().
https://reactnavigation.org/docs/en/navigation-prop.html#navigator-dependent-functions

After a fair bit of head scratching and searching - I found an answer to my issue - My problem is that i'm using a drawer navigation rather than stack navigation - pop is not available to a drawer as there isnt an avilable stack -
ref - https://reactnavigation.org/docs/en/navigation-prop.html
answer from - https://github.com/react-navigation/react-navigation/issues/4793

The pop action takes you back to a previous screen in the stack. It takes one optional argument (count), which allows you to specify how many screens to pop back by.
import { StackActions } from '#react-navigation/native';
const popAction = StackActions.pop(1);
navigation.dispatch(popAction);

Related

Preventing state item in all components from changing with useContext()?

I'm trying to control the state of 2 components use useContext() in React-Native. I have a flatmap of cards that render on a screen. Each card has a press-able 'interested' icon. If I click on a card, it shows the details of the card and also the press-able 'interested' icon. Both should share the same state of being pressed and also send a 'interested bool to the backend.
Right now, if I press 'interested' on one card, the all get pressed. Same with the inside icon of the details page. Obviously the 'interested' state in the Context Provider is to broad and changing all of them. How can I filter out the state change?
Here is the card component that gets rendered from a flatlist:
import React, { useState, useEffect, useContext } from 'react';
import { Image, View, Text, TouchableOpacity } from 'react-native';
import { localDate } from 'utils/formatDate';
import PropTypes from 'prop-types';
import { ConcertContext } from '../../../context/ConcertContextProvider';
import LocationIcon from '../../../assets/images/locationIcon.svg';
import InterestedFilledIcon from '../../../assets/images/interested-filled.svg';
import InterestedEmptyIcon from '../../../assets/images/interested-empty.svg';
import ShareIcon from '../../../assets/images/share.svg';
import styles from './styles';
export default function ConcertCard({
gotoConcertPage,
artist,
cover,
concertCity,
scheduledAt,
description,
concertObj,
}) {
const [clickInterest, setClickInterest] = useState(concertObj.is_interested);
const { interested, setInterested, concertInterest } = useContext(ConcertContext);
const handleInterest = () => {
setInterested(prevState => !prevState);
concertInterest(concertObj);
};
useEffect(() => {
setClickInterest(interested);
}, [interested]);
return (
<TouchableOpacity onPress={() => gotoConcertPage(concertObj)}>
<View style={styles.card}>
<View style={styles.cardContent}>
<View style={styles.cropped}>
<Image style={styles.image} source={{ url: cover.url }} />
</View>
<View style={styles.textWrapper}>
<Text style={styles.date}>{localDate(scheduledAt, 'MMM D, h:mm A z')}</Text>
<View style={styles.locationWrapper}>
<LocationIcon style={styles.locationIcon} />
<Text style={styles.location}>{concertCity.name}</Text>
<Text style={styles.location}>{concertCity.address}</Text>
</View>
<Text style={styles.name}>{artist.name}</Text>
<Text style={styles.description}>{description}</Text>
</View>
<View style={styles.border} />
</View>
<View style={styles.icons}>
<View style={styles.sharedIcon}>
<ShareIcon />
</View>
{!clickInterest ? (
<InterestedEmptyIcon onPress={handleInterest} />
) : (
<InterestedFilledIcon onPress={handleInterest} />
)}
</View>
</View>
</TouchableOpacity>
);
}
ConcertCard.propTypes = {
gotoConcertPage: PropTypes.func,
artist: PropTypes.object,
cover: PropTypes.object,
concertCity: PropTypes.object,
scheduledAt: PropTypes.string,
description: PropTypes.string,
concertObj: PropTypes.object,
};
Here is the card details page:
import React, { useState, useEffect, useContext } from 'react';
import { Text, View, Image, ImageBackground, ScrollView, FlatList } from 'react-native';
import PropTypes from 'prop-types';
import Button from 'components/button';
import { localDate } from 'utils/formatDate';
import { ConcertContext } from '../../context/ConcertContextProvider';
import LocationIcon from '../../assets/images/locationIconTwo.svg';
import LittleFriendIcon from '../../assets/images/little-friend.svg';
import Star from '../../assets/images/Star.svg';
import QuestionMarkIcon from '../../assets/images/question-mark.svg';
import ShareIcon from '../../assets/images/ShareIconLarge.svg';
import styles from './concertStyles';
export default function ConcertPageScreen({ navigation, route }) {
const {
concertObj,
name,
artist,
scheduled_at,
interests,
concert_city,
other_cities,
description,
ticket_base_price,
} = route.params;
const { interested, setInterested, concertInterest } = useContext(ConcertContext);
const formatLocation = city => {
return city.slice(0, city.length - 5);
};
const handleInterest = () => {
setInterested(prevState => !prevState);
concertInterest(concertObj);
};
const buyTicket = concertObj => {
console.log('Buy ticked: ', concertObj.id);
};
return (
<>
<View style={styles.wrapper}>
<ImageBackground
style={styles.imageBackground}
imageStyle={{ opacity: 0.3 }}
source={{ url: artist.media }}
/>
<ScrollView vertical>
<View style={styles.headerWrapper}>
<Text style={styles.headerTitle}>{name}</Text>
<Text style={styles.smallText}>{artist.name}</Text>
<Text style={styles.date}>{localDate(scheduled_at, 'MMM D, h:mm A z')}</Text>
<View style={styles.locationWrapper}>
<LocationIcon style={styles.locationIcon} />
<Text style={styles.location}>{concert_city.name}</Text>
<Text style={styles.location}>{concert_city.address}</Text>
</View>
<Text style={styles.description}>{description}</Text>
<View style={styles.interestedWrapper}>
<LittleFriendIcon style={styles.littleMan} />
<Text style={styles.interested}>{interests} Interested</Text>
</View>
<View
style={{
borderTopColor: '#DADADA',
borderTopWidth: 0.2,
borderStyle: 'solid',
alignSelf: 'center',
height: 5,
marginTop: 32,
marginBottom: 28.5,
}}
/>
<View style={styles.tableWrapper}>
<View style={styles.tableHeaderWrapper}>
<Text style={styles.headerTitle}>DATES</Text>
<Text style={styles.headerTitle}>LOCATIONS</Text>
</View>
<View>
<FlatList
data={other_cities}
initialNumToRender={1}
renderItem={({ item }) => (
<View style={styles.tableItemsWrapper}>
<Text style={styles.dateItem}>
{localDate(item.date, 'MMM DD - h:mm A z')}
</Text>
<Text style={styles.locationItem}>{formatLocation(item.city)}</Text>
</View>
)}
concertCityId={item => `item${item.concert_city_id}`}
concertScheduleId={item => `item${item.concert_schedule_id}`}
/>
</View>
</View>
<View style={styles.iconWrapper}>
<View style={{ backgroundColor: '#292929', width: 44, height: 44, borderRadius: 30 }}>
<ShareIcon style={styles.shareIcon} />
</View>
{!interested ? (
<View
style={{ backgroundColor: '#292929', width: 44, height: 44, borderRadius: 30 }}>
<Star style={styles.starIcon} onPress={handleInterest} />
</View>
) : (
<View
style={{ backgroundColor: '#007AFF', width: 44, height: 44, borderRadius: 30 }}>
<Star style={styles.starIcon} onPress={handleInterest} />
</View>
)}
<Image style={styles.roundAvatar} source={{ url: artist.media }} />
</View>
<View style={styles.iconWrapper}>
<Text style={styles.shareIconText}>Share</Text>
<Text style={styles.interestedIconText}>I'm Interested</Text>
<Text style={styles.artistIconText}>Add Artist</Text>
</View>
<View style={styles.ticketWrapper}>
<Text style={styles.ticketPrice}>TICKET PRICES</Text>
<QuestionMarkIcon style={styles.ticketPrice} />
</View>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Text style={styles.ticketLocation}>In-location</Text>
<Text style={styles.ticketLocation}>$ {ticket_base_price}</Text>
</View>
<View
style={{
borderTopColor: '#DADADA',
borderTopWidth: 0.5,
borderStyle: 'solid',
height: 20,
marginTop: 32,
marginBottom: 28.5,
}}
/>
<View style={styles.footerWrapper}>
<Text style={styles.headerTitle}>TICKET GUIDE</Text>
<Text style={styles.ticketDescription}>
This ticket is for a livestream concert. Sign In at www.colortv.me on your browser
to watch on our TV or laptop. On the go? Watch from the COLOR TV mobile app.
</Text>
</View>
</View>
<Button
rightIcon={false}
text="Buy Ticket"
style={styles.button}
onPress={() => buyTicket(concertObj)}
/>
</ScrollView>
</View>
</>
);
}
ConcertPageScreen.propTypes = {
navigation: PropTypes.object,
route: PropTypes.object,
};
and here is the Context Provider:
import React, { createContext, useState } from 'react';
import { concertInterestApi } from 'utils/apiRoutes';
import useFetcher from 'hooks/useFetcher';
import parseError from '../utils/parseError';
export const ConcertContext = createContext(null);
const ConcertContextProvider = ({ children }) => {
const { isLoading, error, fetcher } = useFetcher();
const [interested, setInterested] = useState([]);
const concertInterest = async concertObj => {
try {
await fetcher({
url: concertInterestApi(concertObj.id),
method: concertObj.is_interested ? 'DELETE' : 'POST',
});
} catch ({ response }) {
throw parseError(response);
}
};
return (
<ConcertContext.Provider value={{ interested, setInterested, concertInterest }}>
{children}
</ConcertContext.Provider>
);
};
export default ConcertContextProvider;
Perhaps I'm not wiring this up correctly at all and any suggestions would be welcome to manage state.
You are confused about the shape of your own context. In your Provider, interested is an array (I'm not sure of what) and setInterested is a function to replace that array. With that in mind, hopefully you can see the problem with this:
const { interested, setInterested, concertInterest } = useContext(ConcertContext);
const handleInterest = () => {
setInterested(prevState => !prevState);
concertInterest(concertObj);
};
You are treating interested as a boolean when it is an array. I think it's all of the concerts that this user is interested it? Or perhaps an array of all userIds who are interested in this concert? Or perhaps useState([]) is a mistake and it was always meant to be a boolean?
Either way, I would recommend moving the shared logic from the ConcertCard and ConcertPageScreen into a custom hook that consumes your context and return an onClickInterested handler function. Your hook can take the concert id and/or user id as an argument.

React Native TouchableOpacity OnPress not Working with loop

I have a scrollview in which multiple items are generated with loop. I added TouchableOpacity above these items because i want these objects to be touchable. But when i add a method on onPress method it shows error not a function , is undefined
List_Data Component:
class List_Data extends React.Component {
fetchData = () => {
console.log("DONE");
}
_renderView = () => {
return (
<View style={{flex:1, padding: 20}}>
<View style={styles.container}>
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false} >
{
this.state.Data.map(function (data, index) {
return (
<TouchableOpacity key={index} onPress={() => this.fetchData()}>
<Image source={{uri: data.imageSrc}}
resizeMode={'cover'}
style={{width: '100%', height: imageHeight}}
/>
</TouchableOpacity>
);
})
}
</ScrollView>
</View>
</View>
)
}
render() {
return (
{this._renderView()}
);
}
}
I don't know whats the issue, it just a method which prints on console.
The issue is coming from your .map. Basically you are losing the value of this as you are not using an arrow function. If you change your .map(function(data, index) to .map((data,index) => it should work.
import * as React from 'react';
import { Text, View, StyleSheet, ScrollView, TouchableOpacity, Image } from 'react-native';
import { Constants } from 'expo';
export default class App extends React.Component {
state = {
Data: [
{imageSrc :'https://randomuser.me/api/portraits/men/39.jpg'},
{imageSrc: 'https://randomuser.me/api/portraits/women/38.jpg'},
{imageSrc: 'https://randomuser.me/api/portraits/men/37.jpg'},
{imageSrc: 'https://randomuser.me/api/portraits/women/36.jpg'},
{imageSrc: 'https://randomuser.me/api/portraits/men/35.jpg'},
{imageSrc: 'https://randomuser.me/api/portraits/women/34.jpg'},
]
}
// let's pass something so that we know that it is working
fetchData = (index) => {
alert(`you pressed ${index}`)
}
_renderView = () => {
return (
<View style={{flex: 1, padding: 20}}>
<View style={styles.container}>
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false} >
{
this.state.Data.map((data, index) => { // change this to an arrow function
return (
<TouchableOpacity key={index} onPress={() => this.fetchData(index)}>
<Image source={{uri: data.imageSrc}}
resizeMode={'cover'}
style={{width: 100, height: 100}}
/>
</TouchableOpacity>
);
})
}
</ScrollView>
</View>
</View>
);
}
render() {
return (
this._renderView()
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
}
});
You can see it working in the following snack https://snack.expo.io/#andypandy/map-with-arrow-function
Try keyboardShouldPersistTaps={true} under ScrollView. <ScrollView keyboardShouldPersistTaps={true}>

Adding item click event in react native Grid View

Please find my code below and help me for adding item click listener for the items in the grid view. Please find the link which I followed library link.
And I need to display the name in an alert when the user clicks on each item in the gridlist. Styles are not included in the code
Thanks in Advance
import React, { Component } from 'react';
import {
Platform,
StyleSheet,
Text,
View,
TouchableWithoutFeedback
} from 'react-native';
import GridLayout from 'react-native-layout-grid';
class HomeScreen extends Component {
renderGridItem = (props) => (
<View style={styles.item} >
<View style={styles.flex} />
<Text style={styles.name} >
{props.name}
</Text>
</View>
);
render() {
const items = [];
for (let x = 1; x <= 30; x++) {
items.push({
name: `Grid ${x}`
});
}
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Grid Layout
</Text>
<View style={styles.flex}>
<GridLayout
items={items}
itemsPerRow={2}
renderItem={this.renderGridItem}>
</GridLayout>
</View>
</View>
);
}
}
export default HomeScreen;
Instead of using <View> in your renderGridItem, you could use one of the touchables component (react native doc).
For example with <TouchableOpacity >:
renderGridItem = (props) => (
<TouchableOpacity style={styles.item} onPress={() => this.showAlert(props.name)}>
<View style={styles.flex} />
<Text style={styles.name} >
{props.name}
</Text>
</TouchableOpacity>
);
showAlert = (name) => {
Alert.alert(
'Alert Title',
`The user name is: ${name}`,
[
{text: 'OK', onPress: () => console.log('OK Pressed')},
],
{ cancelable: false }
)
}
Why don't you wrap renderGridItem in a TouchableWithoutFeedback?
renderGridItem = (props) => (
<TouchableWithoutFeedback onPress={()=> Alert.alert(props.name)}>
<View style={styles.item} >
<View style={styles.flex} />
<Text style={styles.name} >
{props.name}
</Text>
</View>
<TouchableWithoutFeedback />
);
Also you will need to import Alert from react-native.

Change active state and flex transition

I have a react component class like below
<View style={{flex: 1}}>
<TouchableOpacity style={styles.FreeCoffee}/>
<TouchableOpacity style={styles.Help}/>
</View>
both touchableopacity components have the value of flex 2 so they equally divided in window. When one of the touchableopacity pressed, I want to make a transition between flex to 2 into 4 so that one box can grow with animation, also mark it as a "active" or "selected" one. I have searched this many many times but since I am a beginner in ReactNative, I couldn't find any proper way to that.
Is this possible or achiveable ?
//Edit Full Code
import React from 'react'
import {ScrollView, Text, View, TouchableOpacity, Button} from 'react-native'
import styles from '../Styles/Containers/HomePageStyle'
export default class HomePage extends React.Component {
constructor(props){
super(props);
/*this.state = {
active : { flex : 8 }
}*/
}
render() {
return (
<View style={styles.mainContainer}>
<View style={{flex: 1}}>
<TouchableOpacity style={styles.FreeCoffee}/>
<TouchableOpacity style={styles.Help}/>
</View>
</View>
)
}
componentWillMount(){
}
animateThis(e) {
}
}
You can use LayoutAnimation to do this. Define state that toggles the styles that are applied to your render and use onPress in the TouchableOpacity to define your function that Calls the LayoutAnimation and setState. Something like the following:
import React from 'react';
import { LayoutAnimation, ScrollView, StyleSheet, Text, View, TouchableOpacity, Button } from 'react-native';
// import styles from '../Styles/Containers/HomePageStyle'
const styles = StyleSheet.create({
mainContainer: {
flexGrow: 1,
},
FreeCoffee: {
backgroundColor: 'brown',
flex: 2,
},
Help: {
backgroundColor: 'blue',
flex: 2,
},
active: {
flex: 4,
borderWidth: 1,
borderColor: 'yellow',
},
});
export default class HomeContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
active: 'neither',
};
this.setActive = this.setActive.bind(this);
}
setActive(active) {
// tells layout animation how to handle next onLayout change...
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
// set active in state so it triggers render() to re-render, so LayoutAnimation can do its thing
this.setState({
active,
});
}
render() {
return (
<View style={styles.mainContainer}>
<View style={{ flex: 1, backgroundColor: 'pink' }}>
<TouchableOpacity
style={[
styles.FreeCoffee,
(this.state.active === 'FreeCoffee') && styles.active]}
onPress={() => {
this.setActive('FreeCoffee');
}}
/>
<TouchableOpacity
style={[
styles.Help,
(this.state.active === 'Help') && styles.active]}
onPress={() => {
this.setActive('Help');
}}
/>
</View>
</View>
);
}
}

React Native custom event

Just starting out with React Native, and was curious on how best to achieve passing events to a parent component from children buttons. So for example:
import React, { Component } from 'react';
import ViewContainer from '../components/ViewContainer';
import SectionHeader from '../components/SectionHeader';
import {
View
} from 'react-native';
class App extends Component {
render() {
return (
<ViewContainer>
<SectionHeader onCreateNew = {() => console.log("SUCCESS")} ></SectionHeader>
</ViewContainer>
);
}
}
module.exports = ProjectListScreen;
and here is my section header. I was attempting to use an Event Emitter, but not working the way I'd hoped, unless I missed something.
import React, { Component } from 'react';
import Icon from 'react-native-vector-icons/FontAwesome';
import EventEmitter from "EventEmitter"
import {
StyleSheet,
TouchableOpacity,
Text,
View
} from 'react-native';
class SectionHeader extends Component {
componentWillMount() {
console.log("mounted");
this.eventEmitter = new EventEmitter();
this.eventEmitter.addListener('onCreateNew', function(){
console.log('myEventName has been triggered');
});
}
_navigateAdd() {
this.eventEmitter.emit('onCreateNew', { someArg: 'argValue' });
}
_navigateBack() {
console.log("Back");
}
render() {
return (
<View style={[styles.header, this.props.style || {}]}>
<TouchableOpacity style={{position:"absolute", left:20}} onPress={() => this._navigateBack()}>
<Icon name="chevron-left" size={20} style={{color:"white"}} />
</TouchableOpacity>
<TouchableOpacity style={{position:"absolute", right:20}} onPress={() => this._navigateAdd()}>
<Icon name="plus" size={20} style={{color:"white"}} />
</TouchableOpacity>
<Text style={styles.headerText}>Section Header</Text>
</View>
);
}
}
const styles = StyleSheet.create({
header: {
height: 60,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#00B9E6',
flexDirection: 'column',
//paddingTop: 25
},
headerText: {
fontWeight: 'bold',
fontSize: 20,
color: 'white'
},
});
module.exports = SectionHeader;
Thanks for your help!
Not sure if this is the right way to go about this, but using this.props.onCustomEvent from the nested Button's onPress event seems to work fine. Please let me know if there is a better way to go about doing this.
App:
import React, { Component } from 'react';
import ViewContainer from '../components/ViewContainer';
import SectionHeader from '../components/SectionHeader';
import {
View
} from 'react-native';
class App extends Component {
render() {
return (
<ViewContainer>
<SectionHeader onCreateNew = {() => console.log("SUCCESS")} ></SectionHeader>
</ViewContainer>
);
}
}
module.exports = ProjectListScreen;
Section Header
import React, { Component } from 'react';
import Icon from 'react-native-vector-icons/FontAwesome';
import {
StyleSheet,
TouchableOpacity,
Text,
View
} from 'react-native';
class SectionHeader extends Component {
render() {
return (
<View style={[styles.header, this.props.style || {}]}>
<TouchableOpacity style={{position:"absolute", left:20}} onPress={() => this.props.onCreateNew()}>
<Icon name="chevron-left" size={20} style={{color:"white"}} />
</TouchableOpacity>
<Text style={styles.headerText}>Section Header</Text>
</View>
);
}
}
const styles = StyleSheet.create({
header: {
height: 60,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#00B9E6',
flexDirection: 'column',
//paddingTop: 25
},
headerText: {
fontWeight: 'bold',
fontSize: 20,
color: 'white'
},
});
module.exports = SectionHeader;
In addition to #arbitez's answer, if you want to maintain scope then you should make a method and bind to it, eg:
Parent:
constructor(props) {
super(props)
// ........
this.onCreateNew=this.onCreateNew.bind(this);
}
onCreateNew() {
console.log('this: ', this);
}
render() {
return (
<ViewContainer>
<SectionHeader onCreateNew = {this.onCreateNew} ></SectionHeader>
</ViewContainer>
);

Resources