How to navigate to other screen using the headerRight on the navigator code? - react-native

The title is very confusing, but the explanation that I will give will be more clear.
I am creating one StackNavigator for my app, and I am defining one icon to be displayed on the header of one of my screens like so:
const navigator= createStackNavigator(
{
Initial: {
screen: Posts,
navigationOptions: {
title: "All Posts",
headerRight: () => {
return (
<TouchableOpacity style={{ padding: 5 }}>
<Feather name={"plus"} size={30} />
</TouchableOpacity>
);
},
},
},
Details: Details,
Create: Create,
},
{
initialRouteName: "Initial",
}
);
const App = createAppContainer(navigator);
export default () => {
return (
<Provider>
<App />
</Provider>
);
};
The problem is that I want to navigate the user to the Create screen when the user presses the icon that I am rendering on the right side of the header, but I do not know how to have access to the navigation.navigate() function that the navigator generates for all the screens. I know that I can define the header on the Posts' screen file, so I have access to the navigation.navigate() function, but I want to know if there is some way to do it on the App.js file.
EDIT
Reading the documentation I saw that the way that I am was creating the navigator is not what the official documentation recommends. I learned to make it like this by one old course, using React Navigation 4.x, and now with React Navigation 6.x I perceived the difference in the creation and changed the way that I was doing it on my app. You can check the documentation for the problem that I was having here

You can prepare your navigation option this way by sending props
options={(props) => ({
headerRight: () => <TouchableOpacity
onPress={()=>props.navigation.navigate('Screen')} style={{ padding: 5 }}>
<Feather name={"plus"} size={30} />
</TouchableOpacity>
})}

Related

How do I pass certain route parameters from a screen function to a navigation header button in React Native

I am new to react native and am basically wondering how to pass information from one screen to another if the button that triggers the shift from one screen to another is in the navigation class.
For example, If I have a screen:
export default function Screen() {
const data = ["a", "b", "c"]
}
and another screen with a SectionList section that requires that information:
const data = [
{
title: "",
data: {route.params?.data},
},
and finally within the navigation tab a custom navbar header:
<Stack.Screen
name="Screen"
component={ChatScreen}
options={({ route }) => ({
headerRight: () => (
<Pressable onPress={() => navigation.navigate("Screen", {data: *get data from
Screen here*)}>
<Ionicons
name="ellipsis-horizontal"
size={25}
color="#5C6AEF"
style={{ marginBottom: 2 }}
/>
</Pressable>
),
})}
/>
basically, how do I call the information from Screen in the Navigation tab to pass to the SectionList screen?
Thanks for the help!

Going back isn't popping navigation corretly with React Navigation 5

The way I have my set up currently is
Root Navigation
HomeNavigation ? AuthNavigation (depending on login or not)
EditProfile
Profile (ProfileStack)
ProfileStack
ProfilePage
ProfileFollowers
ProfileFollowing
Setting
The problem I am having is. When I go to the LandingPage -> ProfilePage (userId 4) -> Following then press navigation.goback() has not problem. It takes me back to the user page. When I try
LandingPage -> ProfilePage (userId 4) -> Following -> ProfilePage (userId 5) -> goBack() the result isn't going back to the following listing page but instead back to the LandingPage.
I thought within React Navigation it follows a history such that going back results in popping off the current page and going back to the last one we visited. Am I not understanding this part? The way I am going back for my navigation is:
return <ProfileStack.Navigator>
<ProfileStack.Screen name="ProfilePage" component={Profile} options={({ route, navigation }) => {
return {
title: "Profile Page",
headerLeft: () => {
return <Ionicons onPress={() => navigation.goBack()} name="md-arrow-round-back" size={30} color="black" style={{ marginLeft: 15 }} />
},
headerRight: () => {
return <Ionicons onPress={() => navigation.navigate('EditProfile')} name="ios-settings" size={30} color="black" style={{ marginRight: 15 }} />
}
}
}
} />
<ProfileStack.Screen name="ProfileFollowers" component={ProfileFollowers} options={() => {
return {
title: 'Follower'
}
}} />
<ProfileStack.Screen name="ProfileFollowing" component={ProfileFollowing} options={() => {
return {
title: 'Following'
}
}}/>
<ProfileStack.Screen name="Setting" component={Settings} />
</ProfileStack.Navigator>
You can still use the following command:
navigation.navigate(Screen)
React navigation is such that it stores screens in a stack. So the second cell in your stack is always the page you navigated to the new page. Finally, using the same command, you will have an experience similar to goBack().
React navigation detects that you are returning to the previous page. So there will be no problem.
Of course, there are ways to solve your problem. But for now, this is the fastest way to get there. And there is no wrong way.

How to update navigation header from screen?

react-native version 0.62
I am making a quiz app and on every new question(a new quiz screen, QuestionScreen.js), I want to update a label in the navigation bar which is declared in App.js
In App.js, the problem here is I don't know how to pass the state questionIndex to HomeScreen or QuestionScreen, normally I thought this would be using props like <Question questionIndex={questionIndex}>. I tried to pass them in options but that doesn't seem to work...
const App = () => {
const [questionIndex, setQuestionIndex] = useState(0);
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='Home'
>
<Stack.Screen
name='Home'
component={HomeScreen}
options={{
title:''
}}
/>
<Stack.Screen
name='QuestionScreen'
component={QuestionScreen}
options={({ navigation }) => ({
headerBackTitleVisible: false,
headerRight: ()=>(<HeaderRight pageIndex={questionIndex}/>),
headerRightContainerStyle: {
alignItems: "flex-end",
paddingRight: 25
},
})}
/>
function HeaderRight(props) {
return (
<Text>{props.pageIndex}/{questions.length-1}</Text>
);
}
const HomeScreen = ({ navigation }) => {
return (
<View style={styles.container} >
<ScrollView>
<TouchableOpacity onPress={()=>
//here I'm unable to retrieve the questionIndex props.
//tried navigation.getParams("questionIndex") or route.params
navigation.navigate('QuestionScreen', {
pageIndex: 0
})
}>
In QuestionScreen.js
function QuestionScreen({route, navigation, options}) {
const { pageIndex } = route.params;
[Full code here on github]https://github.com/liaoxsong/expofirst
I am a newbie to react-native and I come from native mobile development, this Stack.Navigator api looks weird, can't I just manage the navigation header from the screen component itself? That would make things easier.
it is difficult to customize the default header given by react navigation, what you can do is disabling the header and creating your own custom header.
disable header by giving prop headerMode={"none"} in createStackNavigator and have your own custom header on top,
to disable header docs : https://reactnavigation.org/docs/stack-navigator/#headermode

React Navigation (V2 / V3): How to access navigation.state.index in navigationOptions on screen

I'm building a React Native app and for my navigation I use React Navigation (V3). For my UI elements I use React Native Elements.
I have a screen which I reuse in multiple navigators. Sometimes it is at the root of a stack, and sometimes it's pushed on with a stack. I want to programmatically add a headerLeft element to this screen navigationOptions based on its position in the stack, because I either want the default back button to show, or a burger menu icon to show, if the screen is at the root of the stack (because the stack is nested in a drawer).
So I tried the following:
export class ScannerScreen extends Component {
static navigationOptions = ({ navigation }) => ({
headerLeft:
navigation.state.index > 0 ? (
undefined
) : (
<Icon type="ionicon" name="ios-menu" onPress={navigation.toggleDrawer} />
),
title: strings.scannerTitle
});
// ...
The problem is this doesn't work as navigation.state.index is undefined here. How would one do this with React navigation?
Edit:
As per request I added a screenshot of console.log(navigation) (via <Icon />s onPress.)
I found a hacky solution, which is okay but I also dislike it a bit:
const stackConfig: StackNavigatorConfig = {
cardStyle: styles.card,
navigationOptions: {
headerBackTitleStyle: styles.headerBackTitle,
headerStyle: styles.header,
headerTintColor: "black"
}
};
const HomeStack = createStackNavigator(
{ HomeScreen, RestaurantDetailScreen, MenuScreen, ScannerScreen },
{
...stackConfig,
initialRouteName: "HomeScreen",
navigationOptions: ({ navigation }) => ({
headerLeft:
parseFloat(navigation.state.key.slice(-1)) > 1 ? (
undefined
) : (
<Icon
containerStyle={styles.leftIcon}
type="ionicon"
name={getIconName("menu")}
onPress={navigation.toggleDrawer}
/>
),
...stackConfig.navigationOptions
})
}
);
This way it automatically sets it for the whole stack. You can also do this in the respective screen individual navigation options. But this only works, since the automatically assigned key for the screen contains it's index.

how to display headers in react navigation with TabNavigation

I noticed that views in StackNavigation show the header title but if I set those same screens in a TabNavigation it doesn't show a header. It only shows a header if I wrap a StackNavigation either around each tab, or wrap the TabNavigation nested inside a StackNavigation.
Why don't screens in TabNavigation show a header - is that expected behavior? If so, is it better to have a StackNavigation in each tab, or one big StackNavigation around the TabNavigation?
// tabs navigation doesn't show a header title in each screen
const TabsNavigator = TabNavigator({
Home: {
screen:HomeScreen,
},
Profile: {
screen: ProfileScreen,
},
}, {
tabBarOptions: {
activeTintColor: '#e91e63',
},
navigationOptions: {
header: {
visible: true,
},
},
});
Header shows when I wrap it in a StackNavigator
default StackNavigator({
Home: { screen: TabsNavigator },
});
Or is it better to do it this way
export TabsNavigator = TabNavigator({
Home: {
screen:StackNavigator({
Home: { screen: HomeScreen },
}),
},
Profile: {
screen: StackNavigator({Profile: {screen: ProfileScreen}}),
},
}, {
tabBarOptions: {
activeTintColor: '#e91e63',
},
navigationOptions: {
header: {
visible: true,
},
},
});
Even if this is a fairly old question, I had myself this question a couple days ago, so I'll be adding my two cents about it hoping this will be useful for someone else in the future as well.
React Navigation is an amazing product with a high amount of customization, but that also turns out sometimes to be confusing to begin with, which also applies to some sections of the documentation. navigationOptions as of the current version states, is common for all screens but the "list of available navigation options depends on the navigator the screen is added to." https://reactnavigation.org/docs/tab-navigator.html#navigationoptions-used-by-tabnavigator hence the header option doesn't work because it's not available for TabNavigator itself.
Regarding your question on which approach is the best that depends on what do you want to accomplish with the navigation for your app itself. If you put your TabNavigator inside a StackNavigator the header component will be common for all of the tabs inside the TabNavigator, meaning the tab transition will take effect but the header won't move from its top position. If you on the other hand choose to nest a StackNavigator inside every tab, the header will render inside every tab, meaning the header will move along the tab transition animation.
I made a quick demo for you to see the difference, the code is also available if you wanna play with it.
I understand that this question already has an answer to it, but I would like to just show the way that I was able to make this work for myself using React Navigation > 5.x.x.
Juan's answer describes the best way to set this up, but also showing how it's done so that people can understand what the navigation looks like is also a bit of help for us visual learners. This was a little bit hard for me to understand at the time but hoping that it will help others in the future.
I find the best way to look at this is by breaking down the hierarchy of your apps routes. For me mine looked something like this.
LoginNavigation HomeNavigation
| |
LoginScreen RegisterScreen HomeStack ProfileStack
| |
HomesScreen ProfileScreen
My LoginNavigation stack contains two screens the Login screen and Register screen. In my app I don't need tab navigation set here so it's appropriate to just have these screens wrapped in a StackNavigator. On the other hand I have my HomeNavigation stack which contains a Home screen and a Profile screen. The only issue is that my screens are wrapped in a TabNavigator which does not produce a header.
To fix this we actually need to wrap our two screens (HomeScreen & ProfileScreen) in StackNavigators and then wrap everything with the TabNavigator. After realizing this, it actually makes much more sense to do it this way. Why? Well let's say that in my home screen I have some posts and I want the user to be able to see the post and all the comments that come with it. Well I wouldn't setup another Tab for this because that would just be silly. Instead we would add it to our HomeStack navigation instead like so.
HomeNavigation
|
HomeStack ProfileStack
| |
HomesScreen PostScreen ProfileScreen
This structure now seems apparent when looking at something like the ProfileStack where there could be multiple branches to this stack, such as Settings, Followers, Pictures...
Hopefully displaying the structure helps someone understand this as well as it helped me.
Below is my AppStackNavigator file to see the structure in code.
import React from 'react';
import { createStackNavigator } from '#react-navigation/stack';
import Login from '../screens/Login';
import Register from '../screens/Register';
import FirstProfileImage from '../screens/FirstProfileImage';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import Home from '../screens/Home';
import Profile from './../screens/Profile';
const LoginStack = createStackNavigator();
const HomeTabs = createBottomTabNavigator();
const HomeStack = createStackNavigator();
const ProfileStack = createStackNavigator();
const AppStack = createStackNavigator();
const LoginStackNavigator = () => (
<LoginStack.Navigator screenOptions={{headerStyle: {elevation: 0},cardStyle: {backgroundColor: '#ffffff'}}}>
<LoginStack.Screen name="Login" component={Login}/>
<LoginStack.Screen name="Sign Up" component={Register}/>
<LoginStack.Screen name="Profile Image" component={FirstProfileImage}/>
</LoginStack.Navigator>
)
const HomeStackNavigator = () => (
<HomeStack.Navigator>
<HomeStack.Screen name="Home" component={Home} />
</HomeStack.Navigator>
)
const ProfileStackNavigator = () => (
<ProfileStack.Navigator>
<ProfileStack.Screen name="Profile" component={Profile} />
</ProfileStack.Navigator>
)
const HomeTabNavigator = () => (
<HomeTabs.Navigator>
<HomeTabs.Screen name="HomeStack" component={HomeStackNavigator} />
<HomeTabs.Screen name="ProfileStack" component={ProfileStackNavigator} />
</HomeTabs.Navigator>
)
const AppStackNavigator = () => (
<AppStack.Navigator initialRouteName={'HomeScreen'} screenOptions={{headerStyle: {elevation: 0},cardStyle: {backgroundColor: '#ffffff'}}} headerMode='none'>
<AppStack.Screen name="LoginScreen" component={LoginStackNavigator}/>
<AppStack.Screen name="HomeScreen" component={HomeTabNavigator}/>
</AppStack.Navigator>
)
export default AppStackNavigator;
I do agree that this is a bit overkill and that we could eliminate the issue of having to keep track of each individual navigator, but it's what works for now until they add a feature for this. Hopefully this explanation helps a bit. Thanks for coming to my TED talk.
According to React-Navigation TabNavigator Docs there is no header navigationOption. Therefore, when you write the following code you are actually setting a nonexisting value thus what you are doing does not affect anything.
navigationOptions: {
header: { visible: true },
}
Sadly, you need a StackNavigator in this situation.
A general structure that I use is this, create a tab navigator and then render stacks for each tab. In this example I don't want a header on my LiveMap but I do on the other two tabs. The naming is up to you for whatever makes sense. Then inside each stack I render the screens that make sense.
const Tab = createBottomTabNavigator();
const TrainStack = createStackNavigator();
const MapStack = createStackNavigator();
const BusStack = createStackNavigator();
then each stack renders whatever screens I want for that subset
const MapNavigator = () => {
return (
<MapStack.Navigator
initialRouteName="LiveMap"
screenOptions={{headerShown: false}}>
<MapStack.Screen name="Live Map" component={LiveMap} />
<MapStack.Screen name="Show Routes" component={AllRoutes} />
<MapStack.Screen name="Select Routes" component={SelectRoutes} />
</MapStack.Navigator>
);
};
and the tab navigator will render my stacks for each tab.
const MainNavigator = () => (
<Tab.Navigator
tabBarOptions={{
activeTintColor: 'orange',
keyboardHidesTabBar: true,
}}
initialRouteName="Live Map">
<Tab.Screen
name="Buses"
component={BusNavigator}
options={{
tabBarLabel: 'Buses',
tabBarIcon: ({color, size}) => (
<BusIcon height={30} width={30} color={color} />
),
}}
/>
<Tab.Screen
name="Live Map"
component={MapNavigator}
options={{
tabBarLabel: 'Map',
tabBarIcon: ({color}) => (
<View
height={100}
width={100}
style={{
display: 'flex',
alignItems: 'center',
backgroundColor: '#fff',
paddingTop: 8,
borderRadius: 70,
}}>
<MapIcon height={50} width={50} color="orange" />
</View>
),
}}
/>
<Tab.Screen
name="Trains"
component={TrainNavigator}
options={{
tabBarLabel: 'Trains',
tabBarIcon: ({color, size}) => (
<TrainIcon height={30} width={30} color={color} />
),
}}
/>
</Tab.Navigator>