Sending props back to any screen using back button override - react-native

I have only been working with React-Native recently. For this reason, I would need help with the following problem:
I did override the back button action in the header of a stack navigator, to navigate to any screen. I have to "send back" props to the screen xy. I have tried it on several variants, but unfortunately get error messages (because it does not work as I want) or do not get the expected result. I would be grateful for a solution. Thank you!
static navigationOptions = ({navigation}) => ({
title: null,
headerStyle: {backgroundColor: 'transparent'},
headerLeft: <HeaderBackButton
onPress={() => {
this.props.navigation.state.params.returnData(CurrentServiceProvider,'ServiceProvider')
navigation.push('digitalCode'),{ Info1: SomeObject, Info2: SomeObject }
}}
/>
})
Edit:
Screen B:
export class ScreenB extends React.Component {
static navigationOptions = ({navigation}) => ({
title: null,
headerStyle: {backgroundColor: 'transparent'},
headerLeft: <HeaderBackButton onPress={() => {
navigation.state.params.goBackData({SomeObject, 'ObjectIdentifier'});
navigation.push('ScreenA')}
}
/>
})
}
Screen A:
export class ScreenA extends React.Component {
getBackData = (data) => console.log(data)
ForwardNextScreen(){
this.props.navigation.navigate('ScreenB', {goBackData: this.getBackData});
}
}

If you want to send data back to previous screen ,the correct way is to declare callback on navigationParams (Github issue)
ScreenA
getBackData = (data) => console.log(data)
changeScreen = () => this.props.navigation.navigate('ScreenB', { goBackData: this.getBackData })
ScreenB
this.props.navigation.state.params.goBackData({
//your data json or whatever
})
this.props.navigation.pop()
Edit
Please change your from
headerLeft: <HeaderBackButton onPress={() => {
navigation.state.params.goBackData({SomeObject, 'ObjectIdentifier'});
navigation.push('ScreenA')}
}
/>
to
headerLeft: <HeaderBackButton onPress={() => {
navigation.getParam('goBackData')({SomeObject, 'ObjectIdentifier'});
navigation.pop();
}
/>

Related

Is there anyway to turn `options` into a function same like `navigationOptions` do?

Currently, I was taking a course:Multiplatform Mobile App Development with React Native in coursera, and I was stuck at after every lecture because the instructor use react-navigation#2.0.1 but I want to make sure to learn the latest version(v5). In this lecture he created a stack navigator and bring an icon to a screen like,
import {createStackNavigator} from 'react-navigation';
import { Icon } from 'react-native-elements';
const MenuNavigator = createStackNavigator(
{
Menu: {
screen: Menu,
navigationOptions: ({ navigation }) => ({
headerLeft: (
<Icon
name="menu"
size={24}
color="white"
onPress={() => navigation.toggleDrawer()}
/>
),
}),
},
Dishdetail: { screen: Dishdetail },
},
{
initialRouteName: 'Menu'
}
);
Where navigationOptions can be an object or be a function that takes in props.
I convert it like,
import { createStackNavigator } from '#react-navigation/stack';
import { Icon } from 'react-native-elements';
const MenuNavigator = createStackNavigator();
function MenuNavigatorScreen() {
return (
<MenuNavigator.Navigator
initialRouteName="Menu"
screenOptions={HeaderOptions}
>
<MenuNavigator.Screen
name="Menu"
component={Menu}
/>
<MenuNavigator.Screen
name="Dishdetail"
component={Dishdetail}
options={{ headerTitle: 'Dish Detail' }}
/>
</MenuNavigator.Navigator>
);
}
But I was confused how to convert the navigationOptions functionality into my code. Because their docs didn't tell how to trun my options object into a function to bring the navigation prop?
One more thing is he was using drawerIcon,
const MainNavigator = createDrawerNavigator(
{
navigationOptions: {
drawerLabel: 'Login',
drawerIcon: ({ tintColor }) => (
<Icon
name="sign-in"
type="font-awesome"
size={24}
color={tintColor}
/>
),
}
...
But I didn't find anything related drawerIcon in Drawer navigation docs
I heartily thank if anyone helps me to figure out this.
First of all, The options prop can be used to configure individual screens inside the navigator. And headerLeft is a function that returns a React element to display on the left side of the header. When a function is used, it receives several arguments when rendered (onPress, label, labelStyle, and more - check types.tsx for the complete list).
options = {
({
navigation
}) => ({
headerLeft: () => ( <
Icon name = 'menu'
size = {
24
}
color = 'white'
onPress = {
() =>
navigation.toggleDrawer()
}
/>
)
})
}
And for drawerIcon use:
options = {
{
drawerIcon: ({
tintColor
}) => ( <
Icon name = 'home'
type = 'font-awesome'
size = {
24
}
color = {
tintColor
}
/>
)
}
}

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

React Navigation - How to use this.state in Header navigationOptions?

I have spent a couple hours to find a code to handle the state in navigationOptions, but I don't get it till now,
I have a code :
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state
return {
headerLeft: <FontAwesome name='arrow-left' size={20} color="#FFF" onPress={() => navigation.navigate('Home')} style={{margin: DeviceWidth*0.04}}/>,
// here I want to show the TextInput if the `HeaderRight pressed` and show the `String` for the first time
headerTitle: this.state.showSearch ? <TextInput
placeholder="this is placeholder"
placeholder="search"
underlineColorAndroid='transparent'
placeholderTextColor= 'gray'
minWidth={DeviceWidth*0.75}
style={{borderWidth:1, borderColor:'grey', backgroundColor:'white', borderRadius:50}}
/> : 'My Patient',
// Here I want to set the state of `showSearch` to visible at `onPress`
headerRight: <FontAwesome name='search' size={20} color="#FFF" onPress={() => params.handleRemove()} style={{margin: DeviceWidth*0.04}}/>,
}
}
componentDidMount () {
this.props.navigation.setParams({ handleRemove: this.removeVehicle })
}
removeVehicle = () => {
this.setState({showSearch: !this.state.showSearch})
}
constructor(props){
super(props);
this.state = {showSearch: false}
}
when I run the code, I have an error
TypeError: undefined is not an object (evaluating '_this3.state.showSearch')
It is possible to show/hide the headerTitle depending on this.state.showSearch?
You can do this in the following easy way
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state
return {
headerTitle: params.showSearch ? 'New Title' : 'Alternate Title'
// Similarly for the rest
}
}
changeTitle = () => {
const {showSearch} = this.state
// Assuming you have access to the navigation props
this.props.navigation.setParams({
showSearch
})
this.setState({showSearch: !showSearch})
}

Open Drawer by clicking in header of containing StackNavigator

This is the component which contains my Drawer
export default class StackInView extends React.Component {
render() {
const Stack = StackNavigator({
DrawerStack: { screen: DrawerInView }
}, {
headerMode: 'float',
});
return (
<View style={{ flex: 1 }}>
<Stack />
</View>
);
}
}
The following is where I define my button. I want to define the button in navigationOptions of the screen, because the button should only appear on the screen with the drawer. But clicking the button doesn't work can you help me pls?
... imports ...
export default class DrawerInView extends React.Component {
static navigationOptions = {
title: "Yeah?",
headerRight: <Button title="Menu" onPress={() => {NavigationActions.navigate("DrawerOpen")}}/>
}
constructor(props) {
super(props);
}
render() {
const Drawer = DrawerNavigator({
"one": {
screen: () => {
return (<TabsInView select="something" />)
},
},
"two": {
screen: () => {
return (<TabsInView select="something else" />)
},
}
})
return (
<View style={{ flex: 1 }}>
<Drawer />
</View>
);
}
}
You can open DrawerNavigation on button click like this.
<Button title="Menu" onPress ={ ( ) => this.props.navigation.openDrawer()} />
Don't put Stack into View. It's hard to understand and you break all props.
And the reason it doesn't work is that navigationOptions in second code is not for the drawer but for the StackNavigator in the first code. So it can't execute drawer's navigation.navigate("DrawerOpen") because it's StackNavigator's.
And I highly recommend you to change your app's hierarchy. It's really hard that child Drawer passes its navigation to parent Stack's right button.
Then, it would look like this.
const MyStack = StackNavigator({
Tabs:{ screen: MyTabs, navigationOptions:(props) => ({
headerRight:
<TouchableOpacity onPress={() => {props.screenProps.myDrawerNavigation.navigate('DrawerOpen')}}>
<Text>Open Drawer</Text>
</TouchableOpacity>
})}
}
, {navigationOptions:commonNavigationOptions})
const MyDrawer = DrawerNavigator({
stack1: {
screen: ({navigation}) => <MyStack screenProps={{myDrawerNavigation:navigation}} />,
},
stack2: {
//more screen
}
})

React Native - How To Access Refs?

I'm trying to access this.refs so I can scroll to the top of a Flat List but it is undefined.
My render method is this:
render() {
return (
<View style={styles.container}>
{ this.state.loading &&
<ActivityIndicator />
}
{ !this.state.loading &&
<FlatList
ref='flatList'
data={this.state.facebookPosts}
onEndReachedThreshold={0}
onEndReached={({ distanceFromEnd }) => {
this._getFacebookPosts(this.state.nextPost);
}}
renderItem={({item}) => <FacebookPost
date={item.created_time}
message={item.message}
image={item.attachments.data[0].media.image.src}
url={item.link}
/>}
/>
}
</View>
)
}
}
As you can see, I have the ref set as 'flatList'.
I then have a tab bar that when you click on one of the tabs, it is meant to scroll to the top of the list. (I am trying just for not to console log out the this.refs but getting undefined)
static navigationOptions = {
tabBarLabel: 'News',
showIcon: true,
tabBarIcon: ({ tintColor }) => (
<Image
source={require('../assets/images/newspaper-icon.png')}
style={[styles.icon, {tintColor: tintColor}]}
/>
),
tabBarOnPress: (scene, jumpToIndex) => {
// this.refs.listRef.scrollToOffset({x: 0, y: 0, animated: true})
console.log(this.refs);
jumpToIndex(scene.index);
}
};
What do I need to do differently to get this.refs to not be undefined inside that onTabBarPress function?
The problems in your code are
ref takes a function not a string
scrollToOffset does not use x, y as parameters. If you want to scroll to a certain index, use scrollToIndex instead.
bind isLoading with refreshing is better than inject your FlatList after loading
The code below should work:
class MyListView extends React.Component {
_listRef;
...
render() {
return (
<View>
<FlatList
ref={ref => {this._listRef = ref; }}
data={this.state.facebookPosts}
renderItem={({item}) => <FacebookPost
date={item.created_time}
message={item.message}
image={item.attachments.data[0].media.image.src}
url={item.link}
/>}
...
refreshing={this.state.loading}
/>
}
</View>
)
}
scrollToIndex = (idx) => {
this._listRef.scrollToIndex({ index: idx, animated: true });
}
}
In my option, you only can access to refs that are in the same class.
You do what you wanna do but using componentDidMount when it mounts you scroll the list
It appears that you are trying to access a property on the class instance inside a static property which knows nothing about the instance. Static properties are tied to the class not the class instance and so their this keyword is not a reference to the class instance.
It looks like you are using react-navigation and so you could hack the instance (this) into your static navigationOptions property by doing this in componentDidMount.
componentDidMount() {
this.props.navigation.setParams({ instance: this });
}
And then you can define your navigationOptions as a function like this to get access to your navigation params.
static navigationOptions = ({ navigation }) => ({
tabBarLabel: 'News',
showIcon: true,
tabBarIcon: ({ tintColor }) => (
<Image
source={require('../assets/images/newspaper-icon.png')}
style={[styles.icon, {tintColor: tintColor}]}
/>
),
tabBarOnPress: (scene, jumpToIndex) => {
navigation.state.params.instance.refs.listRef.scrollToOffset({x: 0, y: 0, animated: true})
jumpToIndex(scene.index);
}
});
This may not be the best way, but it is a way to accomplish it.