react-navigation: Navigate to a different screen from a button in header - react-native

I have a Icon on the right side of my header and on press of that button i want to navigate to a different screen.
I have searched very much for this but all of the solutions are for class components and there are no official documentation available for it.
I am using react native version 0.61.4.
On press of the icon in the header on the right i want to move the 'ProfileScreen'. All the other navigation is working fine. I have a button in 'HomeScreen' to move to 'ResultsScreen' but cannot go to 'ProfileScreen' from the header.
Here is snippet of my code
const Stack = createStackNavigator();
const App = () => {
return (
<SafeAreaView style={{ flex: 1 }}>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={
{
title: 'Home',
headerStyle: {
backgroundColor: '#273469',
},
headerTintColor: '#EBF2FA',
headerRight: () => (
<Icon
onPress={() => navigate('ProfileScreen')}
name="edit"
type="material"
/>
),
}
}
/>
<Stack.Screen
name="ResultsScreen"
component={ResultsScreen}
/>
<Stack.Screen
name="ProfileScreen"
component={ProfileScreen}
/>
</Stack.Navigator>
</NavigationContainer>
</SafeAreaView>
)
}

options can take a function as an argument and this function takes props as a parameter.
Here is the documentation
Here is the TypeScript definition for information:
* Navigator options for this screen.
*/
options?: ScreenOptions | ((props: {
route: RouteProp<ParamList, RouteName>;
navigation: any;
}) => ScreenOptions);
as you can see props, contain the navigation object that you can use to call navigate like this :
options={({ navigation }) => ({
title: 'Home',
headerStyle: {
backgroundColor: '#273469',
},
headerTintColor: '#EBF2FA',
headerRight: () => (
<Icon
onPress={() => navigation.navigate('ProfileScreen')}
name="edit"
type="material"
/>
),
})}

Adding to Kevin's answer, you can also add a simple button in the header:
options={({ navigation }) => ({
title: 'Home',
headerStyle: {
backgroundColor: '#273469',
},
headerTintColor: '#EBF2FA',
headerRight: () => (
<Button // a button in the header!
onPress={() =>
navigation.navigate('Account')}
title="Account"
/>
),
})}

Here is what worked for me:
<Stack.screen
name="YourScreenName"
headerShown=true
options={({ navigation }) => ({
title: 'YourOtherScreenName',
headerRight: () => (
<TouchableOpacity
onPress={() => navigation.dispatch(StackActions.push('articleEdit', {articleId: 'new'}))} >
<View style={SomeStyleWithPaddingYouLike}>
<JSX_That_Renders_Your_Icon />
</View>
</TouchableOpacity>
),
})}
/>

Related

React Native Drawer Navigation show headerLeft

I have a Drawer navigator which is inside a Stack navigator and I'd like to display a header. Currently I can display everything I want however because the header is defined at the Stack level the navigation inside the header is stack level not drawer level which is preventing me from opening the drawer.
Root stack
<Stack.Navigator
initialRouteName={"Splash"}
screenOptions={{}}
component={SplashScreen}
>
{ auth ?
<Stack.Screen name="Drawer" component={DrawerStack} options={({ navigation }) => ({
title: 'My App',
headerLeft: () => (
<HeaderLeft navigation={ navigation } />
),
headerRight: () => (
<HeaderRight navigation={ navigation } />
),
headerTitleAlign: 'center',
headerTintColor: 'white',
headerStyle: {
backgroundColor: '#5742f5'
},
})} />
:
<Stack.Screen name="Auth" component={AuthStack} options={{
headerShown: false
}}/>
}
</Stack.Navigator>
Drawer stack
<Drawer.Navigator options={{
headerShown: true,
headerLeft: () => (
<HeaderLeft navigation={ navigation } />
),
}}>
<Drawer.Screen
name="Conversations"
options={{
title: props.title,
}}
component={ChatListScreen}
/>
<Drawer.Screen
name="ChatRoom"
options={{
drawerLabel: () => null,
title: null,
drawerIcon: () => null
}}
component={ChatRoomScreen}
/>
</Drawer.Navigator>
Note in the drawer navigator the line with headerLeft does nothing and is there to show where I attempted to put it thinking it would work. I did think it might be overlaying the stack one so I commented out the stack one and it didn't work.
HeaderLeft
export default function HeaderLeft ({ navigation }) {
const openMenu = () => {
navigation.toggleDrawer();
}
return (
<View style={styles.header}>
<Icon name='menu' onPress={openMenu} size={28} style={styles.icon} color="white"/>
</View>
)
}
My question is how can I refactor this to enable me to have the HeaderLeft component work to open the drawer. I will be adding more screens so ideally something I don't have to pass to each screen but if that is what works I am good with it too.
Options in DrawerStack not work. I modified it:
<Drawer.Navigator
screenOptions={{
headerLeft: () => <HeaderLeft />,
}}>
// ...
</Drawer.Navigator>
And a little change in HeaderLeft:
import { useNavigation } from '#react-navigation/native';
function HeaderLeft() {
const navigation = useNavigation();
const openMenu = () => {
navigation.toggleDrawer();
};
// render your Button
}
Demo: https://snack.expo.dev/#pqv2210/0d613b

How can I navigate react native pages using stack screen headerLeft and headerRight?

I am working on a react native project and I would like to customize the react stack navigator. The default "back button" of a stack navigator screen gives the name of the previous screen and a back arrow. I would like to change the name and more importantly remove the back button carrot.
I visited the React Native documentation and the answer seems to be using the "headerLeft" option, but I don't know how to navigate back to my previous screen. If I use onPress={() => navigation.navigate({routeName: 'Home'})}
then I get an error saying ReferenceError: Can't find variable: navigation
So how I can I navigate my stack navigator using headerLeft and headerRight?
The piece of code giving the error is:
<Stack.Screen
name="ChoresOptionsPage"
component={ChoresOptionsPage}
options={{
headerBackTitle: "Cancel",
headerTitle: "Add Chores",
headerLeft: () => (
<Button
onPress={() => navigation.navigate({routeName: 'Home'})}
title="Cancel"
color="#fff"
/>
),
}}
/>
Here is my file code: -- App.js --
const Stack = createStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: "#FFA06A"
},
headerTintColor: "#fff",
headerTitleStyle: {
fontWeight: "bold",
},
}}
>
<Stack.Screen
name="Home"
component={Home}
options={{
headerTitle: (props) => <Title></Title>,
}}
/>
<Stack.Screen
name="ChoresOptionsPage"
component={ChoresOptionsPage}
options={{
headerBackTitle: "Cancel",
headerTitle: "Add Chores",
headerLeft: () => (
<Button
onPress={() => navigation.navigate({routeName: 'Home'})}
title="Cancel"
color="#fff"
/>
),
}}
/>
<Stack.Screen
name="ChoreLibrary"
component={ChoreLibrary}
options={{
headerTitle: "Chore Library",
headerBackTitle: "Cancel",
headerRight: () => (
<Button
onPress={() => alert('This is a button!')}
title="Save"
color="#fff"
/>
),
}}
/>
<Stack.Screen
name="CustomChore"
component={CustomChore}
options={{
headerTitle: "Custom Chore",
headerBackTitle: "Cancel",
headerRight: () => (
<Button
onPress={() => alert('This is a button!')}
title="Save"
color="#fff"
/>
),
}}
/>
</Stack.Navigator>
</NavigationContainer>
)
}
I think you can pass a function that expects the navigation object, to the options prop, kind of what is done in this example from the docs.
So the result, for your piece of code, would be something like:
<Stack.Screen
name="ChoresOptionsPage"
component={ChoresOptionsPage}
options={({navigation}) => ({
headerBackTitle: "Cancel",
headerTitle: "Add Chores",
headerLeft: () => (
<Button
onPress={() => navigation.navigate({routeName: 'Home'})}
title="Cancel"
color="#fff"
/>
),
})}
/>

Can't call a nested navigation screen from a deep component in react native

I have followed this tutorial and created Drawer and Main Tab Navigations for my react native expo app.
Now I need a screen which should not be listed in Drawer or Tab and which is needed to be called from a deep component where navigations props are not being sent.
I tried useNavigation Hook but got error where react native is unable to find any such screen name.
PFB the tentative sample codes:
Main Tab called from App.js
const Tab = createMaterialBottomTabNavigator();
const HomeStack = createStackNavigator();
const ProfileStack = createStackNavigator();
const ListStack = createStackNavigator();
const MainTabScreen = () => (
<Tab.Navigator
initialRouteName="Home"
activeColor="#fff"
barStyle={{ backgroundColor: '#009387' }}
>
<Tab.Screen
name="Home"
component={HomeStackScreen}
options={{
tabBarLabel: 'Home',
tabBarColor: '#009387',
tabBarIcon: ({ color }) => (
<Icon name="home" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="Profile"
component={ProfileStackScreen}
options={{
tabBarLabel: 'Profile',
tabBarColor: '#1f65ff',
tabBarIcon: ({ color }) => (
<Icon name="aperture" color={color} size={26} />
),
}}
/>
</Tab.Navigator>
);
export default MainTabScreen;
const HomeStackScreen = ({navigation}) => (
<HomeStack.Navigator screenOptions={{
headerStyle: {
backgroundColor: '#009387',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold'
}
}}>
<HomeStack.Screen name = "Home" component = {Home}
options = {{
title: 'Overview',
headerLeft: () => (
<Icon.Button name="menu" size={25}
backgroundColor="#009387" onPress={()=>navigation.openDrawer()}
></Icon.Button>
)
}}
/>
</HomeStack.Navigator>
);
const ProfileStackScreen = ({navigation}) => (
<ProfileStack.Navigator screenOptions={{
headerStyle: {
backgroundColor: '#d02860',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold'
}
}}>
<ProfileStack.Screen name = "Profile" component = {Profile}
options = {{
headerLeft: () => (
<Icon.Button name="menu" size={25}
backgroundColor="#d02860" onPress={()=>navigation.openDrawer()}
></Icon.Button>
)
}}
/>
</ProfileStack.Navigator>
);
const ListStackScreen = ({navigation}) => (
<NotificationsStack.Navigator screenOptions={{
headerStyle: {
backgroundColor: '#694fad',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold'
}
}}>
<ListStack.Screen name = "List" component = {List}
options = {{
headerLeft: () => (
<Icon.Button name="menu" size={25}
backgroundColor="#694fad" onPress={()=>navigation.openDrawer()}
></Icon.Button>
)
}}
/>
</NotificationsStack.Navigator>
);
useNavigation component section:
import { useNavigation } from '#react-navigation/native';
.
.
.
const SomeStuff = ({item}) => {
......
<ListButton title={Count} screenName="ListStackScreen" />
.....
}
.
.
function ListButton({ title, screenName }) {
const navigation = useNavigation();
return (
<Button
title={`${title} Total`}
onPress={() => navigation.navigate(screenName)}
/>
);
}
also tried:
navigation.navigate('HomeDrawer',{screen: screenName})
I need to call the above ListStackScreen from a deep component. I tried using navigation.navigate(ListStackScreen) but it doesn't work as explained above.
Please let me know how to use the screen without displaying it in any Drawer or Tab visually.
Edit: Update after trying the given answer
I do have this in the main app.js also:
<Drawer.Screen name="HomeDrawer" component={MainTabScreen} />
This setup should allow you to navigate between HomeStackScreen and ListStackScreen.
const Stack = createStackNavigator();
function HomeStack() {
return (
<Stack.Navigator
initialRouteName="HomeStackScreen">
<Stack.Screen
name="HomeStackScreen"
component={HomeStackScreen}
/>
<Stack.Screen
name="ListStackScreen"
component={ListStackScreen}
/>
</Stack.Navigator>
);
}
<NavigationContainer>
<Tab.Navigator
initialRouteName="HomeStack">
<Tab.Screen
name="HomeStack"
component={HomeStack}
}}
/>
…
You can nest stacknavigator into Drawer, the inner stacknavigator screen won‘t be shown in Drawer.
I have created an sample

React-navigation - goBack behaviour in nested navigators (stack inside drawer)

I'm using a stack navigator inside drawer navigator - recently upgraded from v4. Trying to implement custom back button on headerLeft. goBack function on the stack screen is going back on the drawer navigator instead of the stack. I don't know if I'm missing something or if it's a bug on v5. The goBack should go to the previous screen in the stack not the drawer. See the gif below; using the gesture goes back on the stack and the default back button on the header goes back onto the stack too. It's only my custom back button with the problem.
export function BlogsStack({navigation}) {
return (
<Stack.Navigator
initialRouteName={'Blogs'}
screenOptions={{
gestureEnabled: true,
gestureDirection: 'horizontal',
cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS,
headerStyle: {
borderBottomWidth: 0,
elevation: 0,
shadowOpacity: 0,
},
headerTintColor: themeVars.headerTintColor,
headerBackground: () => {
return <HeaderBackground />;
},
}}>
<Stack.Screen
name="Blogs"
component={Blogs}
options={{
title: 'Blogs',
headerTitle: () => (
<View>
<HeaderButton title={'Blogs'} />
</View>
),
headerLeft: () => (
<TouchableOpacity
onPress={() => navigation.toggleDrawer()}
style={drawerStyles.menuIconContainer}>
<FeatherIcon
style={drawerStyles.menuIcon}
name="menu"
size={themeVars.hamburgerIconSize}
color={themeVars.hamburgerIconColor}
/>
</TouchableOpacity>
),
headerRight: () => <View />,
}}
/>
<Stack.Screen
name="BlogSingle"
component={BlogSingle}
options={{
headerTitle: () => (
<View>
<HeaderButton title={'Blog'} />
</View>
),
headerLeft: () => (
<TouchableOpacity
onPress={() => navigation.goBack()}
style={drawerStyles.menuIconContainer}>
<FeatherIcon
style={drawerStyles.menuIcon}
name="chevron-left"
size={themeVars.hamburgerIconSize}
color={themeVars.hamburgerIconColor}
/>
</TouchableOpacity>
),
headerRight: () => <View />,
}}
/>
</Stack.Navigator>
);
}
export class Navigation extends Component {
constructor(props, context) {
super(props, context);
}
render() {
return (
<NavigationContainer ref={navigationRef}>
<AppDrawer.Navigator
initialRouteName={'Home'}
drawerContent={props => <DrawerContent {...props} />}
drawerContentOptions={{
labelStyle: {
fontFamily: themeVars.boldFont,
color: themeVars.primaryColor,
},
activeTintColor: 'black',
activeBackgroundColor: 'black',
inactiveTintColor: 'white',
inactiveBackgroundColor: 'white',
itemStyle: {
marginVertical: 0,
borderWidth: 1,
borderColor: 'red',
margin: 0,
padding: 0,
},
}}>
<AppDrawer.Screen
name="Home"
component={HomeStack}
initialRouteName={'Home'}
options={{
drawerLabel: 'Home ',
drawerIcon: () => (
<FeatherIcon
color={themeVars.primaryColor}
name="home"
size={themeVars.drawerIconSize}
/>
),
}}
/>
<AppDrawer.Screen
initialRouteName="Blogs"
backBehavior="order"
name="Blogs"
component={BlogsStack}
options={{
drawerLabel: 'Blogs ',
drawerIcon: () => (
<FontAwesome5
color={themeVars.primaryColor}
name="wordpress"
size={themeVars.drawerIconSize}
/>
),
}}
/>
</AppDrawer.Navigator>
</NavigationContainer>
);
}
}
I sorted out my issue by following the docs. The issue was that I was passing the wrong navigation.
You need to use the correct navigation prop in your header, i.e. by
defining a callback for the options:
https://reactnavigation.org/docs/screen-options#options-prop-on-screen
You're using navigation prop passed from the parent navigator which is
a drawer, so the action is performed in the drawer.
Follow the git issue: https://github.com/react-navigation/react-navigation/issues/8806
To be more clear : we have to get the navigation prop from specific screen itself hence the screen stack will be notified of the history. using hook doesn't do this.

Dynamic items in Drawer nested in Stack Navigator react-navigation

i`m realy stumped about this.
I have a bottom tabs width 4 screens
BottomTabs.js
export const AllTabs = ({ navigation }) => {
return (
<Tab.Navigator initialRouteName="Home" tabBar={props => <BottomNavigation {...props} />}>
<Tab.Screen name="Home" component={HomeStack} options={{
icon: 'shopping-store'
}}/>
<Tab.Screen name="Catalog" component={CatalogStack} options={{
icon: 'pulse'
}}/>
<Tab.Screen name="Cart" component={CartStack} options={{
icon: 'shopping-basket'
}} />
<Tab.Screen name="Profile" component={ProfileStack} options={{
icon: 'person'
}}/>
</Tab.Navigator>
)
}
In first Screen named Home, in HomeStack:
export const HomeStack = () => {
return (
<Stack.Navigator screenOptions={screenOptions} >
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="SectionDrawer" component={SectionDrawer} />
<Stack.Screen name="Detail" component={DetailScreen} />
</Stack.Navigator>
)
}
I calling drawer component in second screen
Drawer
export const SectionDrawer = (props) => {
const { route, navigation } = props
const { list } = route.params
return (
<Drawer.Navigator drawerContentOptions={{ activeBackgroundColor: '#5cbbff', activeTintColor: '#ffffff' }} >
<Drawer.Screen name="Section" component={SectionScreen} options={{ route, navigation }}/>
</Drawer.Navigator>
)
}
And finally in SectionScreen i`m fetcing data with api by id
SectionScreen
export const SectionScreen = (props) => {
console.log(props);
const {route, navigation} = props
const { sectionID } = route.params
const [isLoading, setLoading] = useState(true);
const [productList, setProductList] = useState([]);
useEffect(() => {
fetch('url, {
method: 'GET'
})
.then((response) => response.json())
.then((json) => setProductList(json))
.catch((error) => console.error(error))
.finally(() => setLoading(false));
}, []);
return (
<Content padder>
<Text>section screen.</Text>
<Text>Requested id {sectionID}</Text>
{isLoading ? (
<Spinner />
) : (
<ProductCards list={productList} perLine={2} navigation={navigation}/>
)}
</Content>
)
}
I calling SectionScreen in drawer with this construction:
<CardItem cardBody button onPress={() => {
navigation.dispatch(
CommonActions.navigate({
name: 'SectionDrawer',
params: {
screen: 'SectionScreen',
params: {
title: card.title,
sectionID: card.id
}
},
})
);
}}>
All of this works ... but I need to push in drawer dynamic links fetched from api
I am tried: in Drawer
<Drawer.Navigator drawerContentOptions={{ activeBackgroundColor: '#5cbbff', activeTintColor: '#ffffff' }} >
{list.map((item, index) => <Drawer.Screen name={item.title} component={SectionScreen} key={index}/>)}
</Drawer.Navigator>
and in place where I calling SectionScreen:
<CardItem cardBody button onPress={() => {
navigation.dispatch(
CommonActions.navigate({
name: 'SectionDrawer',
params: {
list,
screen: card.title,
params: {
title: card.title,
sectionID: card.id
}
},
})
);
}}>
After this I see items that I passed in list param,
but when i try to navigate to them, i have error in SectionScreen route.param.id is not defined, and route.params is undefined too.
Also i trying to fetch data that I need in drawer, in drawer component, and draw it with
drawerContent={props => <CustomDrawerContent {...props} />}
and render them with:
{catalogSections.map((item, index) => <DrawerItem label={item.title} focused onPress={() => drawerNavigate(navigation, item)} key={index}/>)}
and it works fine but i cannot select active item from them...
help me please how right to add items to drawer and navigate always to one component with different params