React Native, sending list through a component with withNavigation - react-native

My question is somewhat unique than many other similar ones: I want to pass some list to another screen through a (functional) component. It doesn't work.
App.js has:
const switchNavigator = createSwitchNavigator({
locationAllFlow: createStackNavigator(
{
locationsFlow: createStackNavigator(
{
Locations: { screen: LocationsScreen },
Search: { screen: SearchScreen }
},
{
headerMode: 'none'
}
),
LocationDetail: LocationDetailScreen
},
{
headerMode: 'none'
}
),
...
Then LocationsScreen has:
<TitleBar
title='Locations'
hasSearch={true}
list={results.map(result => result.name)}
/>
and LocationsScreen has TitlBar as a component:
const TitleBar = ({ navigation, title, hasSearch, list }) => {
...
<TouchableOpacity
style={{ padding: 8, position: 'absolute', right: 4 }}
onPress={() => {
console.log('inside onPress: ', list);
navigation.navigate('Search', { list });
}}
>
<MaterialIcons style={styles.titleIcon} name='search' />
</TouchableOpacity>
...
export default withNavigation(TitleBar);
and finally, need to go to SearchScreen:
const SearchScreen = ({ list }) => {
console.log('SearchScreen: list.length: ', list);
return (
<View style={{ backgroundColor: 'rgb(15, 104, 186)', flex: 1 }}>
<SafeAreaView>
<FlatList
data={list}
keyExtractor={item => item}
renderItem={({ item }) => (
<TouchableOpacity>
<Text>item</Text>
</TouchableOpacity>
)}
/>
</SafeAreaView>
</View>
);
};
Trouble happens in the SearchScreen: though inside onPress: prints list very well, somehow, SearchScreen: list.length: prints undefined. Why?

list won't be passed as params to SearchScreen. You should use navigation.getParam(paramID, defaultValue) instead
Docs reference: https://reactnavigation.org/docs/en/params.html

Related

Conditionally toggling element in React-native overlap the element instead replace

I have a header where i want to show menu or back Icon depending on the current page. so i am rendering the Icon conditionally like bellow
import Icon from 'react-native-vector-icons/Ionicons';
const HeaderContainer = ({navigation}) => {
return (
<SafeAreaView forceInset={{top: 'never'}}>
<View
style={{
width: '100%',
flexDirection: 'row',
alignItems: 'center',
paddingLeft: 10,
paddingRight: 10,
}}>
<View>
{navigation.state.routeName !== 'Home' ? (
<Icon
name="chevron-back-sharp"
size={30}
onPress={() => {
navigation.goBack();
}}
/>
): (<Icon
name="ios-menu"
size={30}
onPress={() => {
navigation.toggleDrawer();
}}
/>)}
</View>
</SafeAreaView>
const CustomHeader = ({navigation}) => {
return {
header: (props) => <HeaderContainer {...props} />,
headerStyle: {backgroundColor: '#fff'},
headerTintColor: '#000',
};
};
const AppNavigator = createStackNavigator(
{
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
Cart: { screen: CartScreen },
},
{
initialRouteName: 'Home',
defaultNavigationOptions: ({navigation}) => {
return CustomHeader(navigation);
},
},
);
const AppDrawerNavigator = createDrawerNavigator(
{ App: { screen: AppNavigator } },
{contentComponent: DrawerContainer},
);
export default AppContainer = createAppContainer(AppDrawerNavigator);
But initially it is loading with only one Icon, i.e menu icon, but when i change the navigation, getting menu and back icon overlapped. please help me how can i fix this
screenshot
Firstly, I would console.log the navigation right before returning the header to see how the routeName is changing. That might be happening because the initialRouteName is 'Home'.
Edit: you could store the routeName in a variable right before return and call it isHome = navigation.state.routeName and instead of having two icons between which you choose, put just one and change its props depending on isHome.
<Icon
name= !isHome ? "chevron-back-sharp" : "ios-menu"
size={30}
onPress={() => !isHome ? navigation.goBack() : navigation.toggleDrawer()}
/>
Another solution would be to have a param on Home screen, when it mounts
componentDidMount() {
this.props.navigation.setParams({isHome: true})
}
and then, isHome = navigation.state && navigation.state.params && navigation.state.params.isHome

Passing data from one page to another via StackNavigator in DrawerNavigator

For a hobby project I am building an app where my friends can check out our planned group events. Whenever someone presses on an event I want to show a screen with details about that specific event. So I want to go from my EventScreen which shows a FlatList with Events, to EventDetailScreen. Which needs to show one specific event.
So far I've got the navigation part working, but I cannot pass any data to the next screen...
I have tried to send the event as a param several ways. But I can't figure out what to do next. I've read something about needing to pass data from my DrawerNavigator to my StackNavigator, but when I tried this I got an error saying I need to define my navigation in the AppContainer.
MenuNavigator.js
//Navigation Drawer Structure for all screen
class NavigationDrawerStructure extends Component {
//Top Navigation Header with Donute Button
toggleDrawer = () => {
//Props to open/close the drawer
this.props.navigationProps.toggleDrawer();
};
render() {
return (
<View style={{ flexDirection: 'row' }}>
<TouchableOpacity onPress={this.toggleDrawer.bind(this)}>
<Ionicons
name="md-menu"
color="white"
size={32}
style={styles.menuIcon}
/>
</TouchableOpacity>
</View>
);
}
}
//Stack Navigator for the First Option of Navigation Drawer
const HomeScreen_StackNavigator = createStackNavigator({
//All the screen from the First Option will be indexed here
HomeScreen: {
screen: HomeScreen,
navigationOptions: ({ navigation }) => ({
headerLeft: <NavigationDrawerStructure navigationProps={navigation} />,
headerStyle: {
backgroundColor: '#000',
},
}),
},
});
//Stack Navigator for the Second Option of Navigation Drawer
const EventsScreen_StackNavigator = createStackNavigator({
//All the screen from the Second Option will be indexed here
EventsScreen: {
screen: EventsScreen,
navigationOptions: ({ navigation }) => ({
headerLeft: <NavigationDrawerStructure navigationProps={navigation} />,
headerStyle: {
backgroundColor: '#000',
},
}),
},
});
//Stack Navigator for the Third Option of Navigation Drawer
const CalendarScreen_StackNavigator = createStackNavigator({
//All the screen from the Third Option will be indexed here
CalendarScreen: {
screen: CalendarScreen,
navigationOptions: ({ navigation }) => ({
headerLeft: <NavigationDrawerStructure navigationProps={navigation} />,
headerStyle: {
backgroundColor: '#000',
},
}),
},
});
//Stack Navigator for the Fourth Option of Navigation Drawer
const PollScreen_StackNavigator = createStackNavigator({
//All the screen from the Third Option will be indexed here
PollScreen: {
screen: PollScreen,
navigationOptions: ({ navigation }) => ({
headerLeft: <NavigationDrawerStructure navigationProps={navigation} />,
headerStyle: {
backgroundColor: '#000',
},
}),
},
});
//Stack Navigator for the Fifth Option of Navigation Drawer
const InfoScreen_StackNavigator = createStackNavigator({
//All the screen from the Third Option will be indexed here
InfoScreen: {
screen: InfoScreen,
navigationOptions: ({ navigation }) => ({
headerLeft: <NavigationDrawerStructure navigationProps={navigation} />,
headerStyle: {
backgroundColor: '#000',
},
}),
},
});
//Stack Navigator for the EventDetailScreen of Navigation Drawer
const EventDetailScreen_StackNavigator = createStackNavigator({
//All the screen from the Third Option will be indexed here
EventDetailScreen: {
screen: EventDetailScreen,
navigationOptions: ({ navigation }) => ({
headerLeft: <NavigationDrawerStructure navigationProps={navigation} />,
headerStyle: {
backgroundColor: '#000',
},
}),
},
});
const DrawerMenu = createDrawerNavigator(
{
HomeScreen: {
screen: HomeScreen_StackNavigator,
},
EventsScreen: {
screen: EventsScreen_StackNavigator,
},
CalendarScreen: {
screen: CalendarScreen_StackNavigator,
},
PollScreen: {
screen: PollScreen_StackNavigator,
},
InfoScreen: {
screen: InfoScreen_StackNavigator,
},
EventDetailScreen: {
screen: EventDetailScreen_StackNavigator,
},
},
{
// define customComponent here
contentComponent: CustomSidebarMenu,
drawerWidth: 300,
drawerBackgroundColor: 'rgba(0,0,0,0.6)', // or 'rgba(0,0,0,0)'
}
);
const styles = StyleSheet.create({
menuIcon: {
marginLeft: 15,
},
});
export default createAppContainer(DrawerMenu);
Events.js
class EventsScreen extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
dataSource: null,
refreshing: false,
};
}
async componentDidMount() {
const events = await ajax.FetchEvents();
this.setState({
isLoading: false,
dataSource: events,
refreshing: false,
});
}
handleRefresh = () => {
this.setState(
{
refreshing: false,
},
() => {
this.componentDidMount();
}
);
};
itemCard({ item }) {
const { navigate } = this.props.navigation;
return (
<TouchableWithoutFeedback
onPress={() =>
navigate('EventDetailScreen', {
data: 'test',
})
}>
<View style={styles.card}>
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
<Text numberOfLines={1} style={styles.desc}>
{item.description}
</Text>
<View style={styles.row}>
<View style={styles.iconColumn}>
<Ionicons name="md-home" color="white" size={24} />
</View>
<View style={styles.textColumn}>
<Text style={styles.location}>location</Text>
</View>
</View>
<View style={styles.row}>
<View style={styles.iconColumn}>
<Ionicons name="md-calendar" color="white" size={24} />
</View>
<View style={styles.textColumn}>
<Text style={styles.date}>{item.date}</Text>
</View>
</View>
</View>
</View>
</TouchableWithoutFeedback>
);
}
render() {
if (this.state.isLoading) {
return (
<View style={styles.container}>
<ActivityIndicator />
</View>
);
} else {
return (
<View style={styles.container}>
<FlatList
data={this.state.dataSource}
renderItem={({ item }) => this.itemCard({ item })}
keyExtractor={item => item.id.toString()}
onRefresh={() => this.handleRefresh()}
refreshing={this.state.refreshing}
/>
</View>
);
}
}
}
EventDetailScreen.js
class EventDetailScreen extends Component {
render() {
let item = this.props.navigation.getParam('data', 'NO-DATA');
return (
<View>
<Text>{item}</Text>
</View>
);
}
}
export default EventDetailScreen;
Whenever I click on an event, the EventDetailScreen will say 'NO-DATA' as the item is undefined. My intention is to pass the event, the user clicked on, to the next screen. So I can use the title, description etc. from that specific event.
And yes, I know the code is a bit messy. I'm new to React-Native and this is my first app (and also first post), so there's a lot to improve :)
Thanks in advance!
Found out I should use this.props.navigation.push instead of navigate. This way I can pass params to the next screen. Thanks!
For Passing Param
this.props.navigation.navigate('Filter', {
uri: this.state.uri,
});
For Getting Param
const { navigation } = this.props;
const uri = navigation.getParam('uri');
console.log('url', uri);
this.setState({ uri: uri });
When you called setState() its re-render the component. when component renders its make call of the component's lifecycle( eg. componentWillUpdate or componentDidUpdate).
You might be getting the error because you might be setting state in any of the lifecycles methods. Thus it creates a recursive function calls and you reach Maximum update depth exceeded error.
Rahul Jograna's answer is correct.
refer this link for understanding react-native component lifecycle. https://reactjs.org/docs/react-component.html

having problem with react-native navigation | undefined is not an object (evaluating '_this.props.navigation')

hi i'm working on a new react-native app, but i had some issues with the navigation from a component to a screen.
this is the link for the code on snack: https://snack.expo.io/#mimonoux/my-app-navigation-test
i have already tried this
<ButtonCarte onPress={() => this.props.navigation.navigate('Carte') } />.
but it didn't work. please if anyone could help me with this please check the snack link and take a deep look at the easy code i made for my real problem
I saw your problem now. With react-navigation,
navigation props exists in a component when : either the component is configured in your route configuration object that you defined in App.js, either you use the withNavigation HOC ( https://reactnavigation.org/docs/en/with-navigation.html ).
Now in the Medicine_listDetail component this.props.navigation does not exist since Medicine_listDetail does not appear in your route and also the props object should not be read by this.props in a functional component. You can do one of this two way :
const Medicine_listDetail = ({medicine, navigation}) => {
// i'm passing navigation props comme from parent component that have
// navigation object
// ...
}
// OR you can do
const Medicine_listDetail = (props) => {
const { medicine, navigation } = props;
// i'm passing navigation props comme from parent component that have
// navigation object
// ...
}
Hence the following is an attempt at a solution that work for me.
Medicine_listDetail component : i'm passing navigation props come from
parent component that have navigation object
...
const Medicine_listDetail = ({medicine, navigation}) => {
const {title, coordinate} = medicine;
const {
headerContentStyle,
headerTextStyle,
cityTextStyle,
addTextStyle,
infoContainerStyle,
buttonsContainerStyle,
specialityTextStyle,
buttonStyle,
textStyle
} = styles
return (
<View>
<View style={headerContentStyle}>
<Text style={headerTextStyle}>{title}</Text>
</View>
<View style={buttonsContainerStyle}>
<ButtonCarte onPress={() => navigation.navigate('Carte') }>
</ButtonCarte>
</View>
</View>
);
};
...
ButtonCarte component
const ButtonCarte = ({onPress, children}) => {
const {buttonStyle, textStyle} = styles;
return (
<TouchableOpacity onPress={() => onPress()} style={buttonStyle}>
<Ionicons name={'ios-pin'} size={20} color="white" />
<Text style={textStyle}>
Voir La Carte
</Text>
</TouchableOpacity>
);
};
Medicin component : in all_medicine() function, i'm passing navigation object in props of Medicine_listDetail component. So this is the trick.
export default class Medicin extends React.Component {
constructor(props) {
super(props);
this.state = {
list_allMedicine: data_allMedicine,
selectedIndex: 0,
};
this.updateIndex = this.updateIndex.bind(this);
}
updateIndex(selectedIndex) {
this.setState({ selectedIndex });
}
all_medicine() {
const { navigation } = this.props;
return this.state.list_allMedicine.map(medicine => (
<Medicine_listDetail key={medicine.title} medicine={medicine} navigation={navigation} />
));
}
render() {
const buttons = ['Tout', '...', '...', '...'];
const { selectedIndex } = this.state;
return (
<View style={{ flex: 1}}>
<View
style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ButtonGroup
onPress={this.updateIndex}
selectedIndex={selectedIndex}
buttons={buttons}
containerStyle={{ borderRadius:8 }}
/>
</View>
<Divider
style={{
backgroundColor: 'lightgrey',
marginHorizontal: 5,
height: 2,
}}
/>
<View style={{ flex: 5 }}>
{this.state.selectedIndex == 0 ? (
<ScrollView>{this.all_medicine()}</ScrollView>
) : (
<Text>test</Text>
)}
</View>
</View>
);
}
}
At least in App.js, i change the name of carte tab from Cart to Carte because of your RootStack stack.
export default createAppContainer(
createBottomTabNavigator(
{
Home: {
screen: Home,
navigationOptions: {
tabBarLabel: 'Home',
tabBarIcon: ({ tintColor }) => (
<Ionicons name={'ios-home'} size={25} color={tintColor} />
),
},
},
Medicin: {
screen: Medicin,
navigationOptions: {
tabBarLabel: 'Medicin',
tabBarIcon: ({ tintColor }) => (
<Image
source={require('./assets/images/Dashboard/drawable-xhdpi/doctor_heart.png')}
style={{ width: 25, height: 20, tintColor: tintColor }}
/>
),
},
},
Carte: {
screen: Carte,
navigationOptions: {
tabBarLabel: 'Carte',
tabBarIcon: ({ tintColor }) => (
<Ionicons name={'ios-map'} size={25} color={tintColor} />
),
},
},
},
{
tabBarOptions: {
activeTintColor: 'black',
inactiveTintColor: 'gray',
},
}
)
);
I test this and it work for me.
try adding this:
import { NavigationEvents, NavigationActions } from 'react-navigation';
Here is a screenshot of what's available in props in reference to the comments below:
Here is a screenshot of what I mentioned in the comments. You can see where I added a console.log. It shows in the console that although navigation is in this.props, actions within navigation is empty. I think that is the source of the problem. If you put more console.logs like the one I've done you will see where in the project it loses that information.

How to add icons to tabs in react-native-tab-view

I am working with react-native-tab-view and I can just put text on tabs but I want icons. There are some answers on GitHub but they are unclear. If you know anything it would be perfect!
1. Get an imported icon library:-
import Icon from 'react-native-vector-icons/AwesomeFont'
2. Create a method for rendering the icons depending on route using props:-
const getTabBarIcon = (props) => {
const {route} = props
if (route.key === 'Search') {
return <Icon name='search' size={30} color={'red'}/>
} else {
return <Icon name='circle' size={30} color={'red'}/>
}
}
3. Render TabView with a rendered TabBar prop calling back to getTabBarIcon:-
export default class App extends React.Component {
state = {
index: 0,
routes: [
{key: 'Home', title: 'Hello'},
{key: 'Search', title: 'Second'}
],
}
render() {
return (
<TabView
navigationState={this.state}
renderScene={SceneMap({
Home: FirstRoute,
Search: SearchScreen,
})}
onIndexChange={index => this.setState({index})}
initialLayout={{height: 100, width: Dimensions.get('window').width}}
renderTabBar={props =>
<TabBar
{...props}
indicatorStyle={{backgroundColor: 'red'}}
renderIcon={
props => getTabBarIcon(props)
}
tabStyle={styles.bubble}
labelStyle={styles.noLabel}
/>
}
tabBarPosition={'bottom'}
/>
);
}
}
4. You can style the TabBar with anything (here the label is hidden to use icon only tabs)
const styles = StyleSheet.create({
scene: {
flex: 1,
},
noLabel: {
display: 'none',
height: 0
},
bubble: {
backgroundColor: 'lime',
paddingHorizontal: 18,
paddingVertical: 12,
borderRadius: 10
},
})
react-native
You have to override renderHeader method and define in TabBar your own render label method:
renderHeader = (props) => (
<TabBar
style={styles.tabBar}
{...props}
renderLabel={({ route, focused }) => (
<View style={styles.tabBarTitleContainer}>
/* HERE ADD IMAGE / ICON */
</View>
)}
renderIndicator={this.renderIndicator}
/>
);
I had the same problem. I solved it as follows by creating a "_renderTabBar" function and passing as props to the renderTabBar method of the TabView component and in this function I put the component "image" as my icon, I hope it helps.
A print:
_renderTabBar = props => {
const inputRange = props.navigationState.routes.map((x, i) => i);
return (
<View style={styles.tabBar}>
{props.navigationState.routes.map((route, i) => {
const color = props.position.interpolate({
inputRange,
outputRange: inputRange.map(
inputIndex => (inputIndex === i ? 'red' : 'cyan')
),
});
return (
<TouchableOpacity
style={[styles.tabItem, {backgroundColor: '#FFF' }]}
onPress={() => this.setState({ index: i })}>
<Image
style={styles.iconTab}
source={{uri: 'https://www.gstatic.com/images/branding/product/2x/google_plus_48dp.png'}}
/>
<Animated.Text style={{ color }}>{route.title}</Animated.Text>
</TouchableOpacity>
);
})}
</View>
);
};
Here you render
render() {
return (
<TabView
navigationState={this.state}
renderScene={this._renderScene}
renderTabBar={this._renderTabBar}
onIndexChange={index => this.setState({ index })}
/>
);
Code complete: https://snack.expo.io/#brunoaraujo/react-native-tab-view-custom-tabbar

React navigation state.params not working

It is not working. I tried everything. Nothing works. state.params is simply not there if you make an advanced app.
I have this problem with the react "navigation". It says in the manual that the params object should be there https://reactnavigation.org/docs/navigation-prop.html#state-the-screen-s-current-state-route But it isn't.
I set an id parameter like this in screen 1 when I link to screen 2:
<TouchableWithoutFeedback onPress={ ()=> this.props.navigation.navigate('FontsTab', { id: item.id }) } style={styles.listHeader} >
<View style={styles.listRowContainer}>
<View style={styles.listinside1Container}>
<Image style={styles.listImage} source={item.icon} />
<View style={styles.listContainer} onPress={(event) => this._selectedItem(item.text)} >
<Text style={styles.listHeader} >
{item.title}
</Text>
<Text style={styles.listValue} >{item.value}</Text>
<Image
style={{width: 50, height: 50}}
source={{uri: item.img}}
/>
</View>
</View>
</View>
</TouchableWithoutFeedback>
But it's not working. In screen 2 I can't use the state.params :
<ScrollView style={styles.container}>
<Text>{ JSON.stringify(this.props.navigation)}</Text>
<Text>TEST{ state.params }</Text>
<Image
style={{width: 150, height: 150}}
source={{uri: this.state.dataSource.img}}
/>
<Text style={styles.textStyle} >{this.state.dataSource.text}</Text>
</ScrollView>
state.params just returns nothing. What can I do about it?
The full class for screen2:
class Fonts extends Component {
constructor(props) {
super(props);
this.state = {
params: null,
selectedIndex: 0,
value: 0.5,
dataSource: null,
isLoading: true
};
this.componentDidMount = this.componentDidMount.bind(this);
}
getNavigationParams() {
return this.props.navigation.state.params || {}
}
componentDidMount(){
return fetch('http://www.koolbusiness.com/newvi/4580715507220480.json')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
...this.state,
isLoading: false,
dataSource: responseJson,
}, function(){
});
})
.catch((error) =>{
console.error(error);
});
}
render() {
if(this.state.isLoading){
return(
<View style={{flex: 1, padding: 20}}>
<ActivityIndicator/>
</View>
)
}
return (
<ScrollView style={styles.container}>
<Text>{ JSON.stringify(this.props)}</Text>
<Text>TEST{ this.state.params }</Text>
<Image
style={{width: 150, height: 150}}
source={{uri: this.state.dataSource.img}}
/>
<Text style={styles.textStyle} >{this.state.dataSource.text}</Text>
</ScrollView>
);
}
}
In my app this pain is reproducible by a simple button in screen1:
<Button
onPress={() => navigate('FontsTab', { name: 'Brent' })}
title="Go to Brent's profile"
/>
Then switching to the FontsTab works but the params are not in the state object:
I also have this code for the tabview
import React, { Component } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { StackNavigator } from 'react-navigation';
import { Icon } from 'react-native-elements';
import FontsHome from '../views/fonts_home';
import FontsDetails from '../views/fonts_detail';
const FontsTabView = ({ navigation }) => (
<FontsHome banner="Fonts" navigation={navigation} />
);
const FontsDetailTabView = ({ navigation }) => (
<FontsDetails banner="Fonts Detail" navigation={navigation} />
);
const FontsTab = StackNavigator({
Home: {
screen: FontsTabView,
path: '/',
navigationOptions: ({ navigation }) => ({
title: '',
headerLeft: (
<Icon
name="menu"
size={30}
type="entypo"
style={{ paddingLeft: 10 }}
onPress={() => navigation.navigate('DrawerOpen')}
/>
),
}),
},
Detail: {
screen: FontsDetailTabView,
path: 'fonts_detail',
navigationOptions: {
title: 'Fonts Detail',
},
},
});
export default FontsTab;
this.props.navigation.state.params.id will give you the value of param id passed from screen1.
I put my screen in the right StackNavigator and then it worked.