How to create custom header options for each screen multi stack navigation react native? - react-native

I am looking for a way to set custom header options (styling, icons, enabling/disabling back button, left and right options for the header) for each of the screen in the stacks, while maintaining multi-stack architecture for my app in react native?
This is how my App.js looks right now, and I am willing to change it if need be.
const Stack = createStackNavigator();
const App = () => {
const ref = React.useRef();
const { getInitialState } = useLinking(ref, {
prefixes: ['FoodApp://'],
});
const AuthStack = createStackNavigator();
function AuthStackScreen() {
return (
<AuthStack.Navigator>
<AuthStack.Screen name="LogIn" component={LogIn} />
<AuthStack.Screen name="SignUp" component={SignUp} />
</AuthStack.Navigator>
);
}
const AppStack = createStackNavigator();
//I'd like to set different header options for each of the screen here
function AppStackScreen() {
return (
<AppStack.Navigator>
<AppStack.Screen name="MenuCategoryItems" component={MenuCategoryItems} />
<AppStack.Screen name="Delivery" component={Delivery} />
<AppStack.Screen name="Account" component={Account} />
<AppStack.Screen name="Notification" component={Notification} />
<AppStack.Screen name="Cart" component={Cart} />
</AppStack.Navigator>
);
}
//TODO: pass customized bar components
const Tab = createBottomTabNavigator();
//I'd like to set different header options for each of the screen here
function Tabs(){
return (
<Tab.Navigator tabBar={props => <BottomMenu {...props} />}>
<Tab.Screen name="Home" component={Home} />
<Tab.Screen name="Delivery" component={Delivery} />
<Tab.Screen name="Account" component={Account} />
<Tab.Screen name="Notification" component={Notification} />
<Tab.Screen name="Cart" component={Cart} />
</Tab.Navigator>
);
}
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={Tabs} />
<Stack.Screen name="AppStack" component={AppStackScreen} />
/*Should I place something else here so that I have access to both AppStack and Tabs navigations?*/
</Stack.Navigator>
</NavigationContainer>
);
};
export default App;

Not sure if this is what you need but here is how I customize some "borderColor" value for my header component.
You can pass any options you want via the setOptions method of the navigation component. It will be populated in the scene.descriptor.options parameter.
Below an example of how I am using it in my app.
the header options for my screen/navigator.
header: ({scene, previous, navigation}) => {
const {options} = scene.descriptor;
let borderColor = options.borderColor ?? 'yellow';
if (scene.route.name.startsWith('Test')) {
borderColor = 'blue';
}
return <HeaderBar options={options}
title={title}
previous={true}
navigation={navigation}
borderColor={borderColor}
/>;
}
And in any of the screens components I can use
useLayoutEffect(() => {
navigation.setOptions({
borderColor: 'orange'
})
}, [ navigation ])
Now, my <HeaderBar> component will receive a prop borderColor whose value is 'orange'.
export const HeaderBar = ({ options, title, previous, navigation, borderColor = 'yellow' }) => {
[...]
console.log(borderColor); // orange
}
As you can see, my HeaderBar component also receive the full options as a prop, therefore I could skip the borderColor prop and check the options inside the header component itself.
Like so
export const HeaderBar = ({ options, title, previous, navigation }) => {
const { borderColor } = options;
[...]
}
Hope it will help you if not too late.

Well. You could hide header default of react-navigation in your stack.
function AppStackScreen() {
return (
<AppStack.Navigator headerMode={'none'}>
<AppStack.Screen name="MenuCategoryItems" component={MenuCategoryItems} />
<AppStack.Screen name="Delivery" component={Delivery} />
<AppStack.Screen name="Account" component={Account} />
<AppStack.Screen name="Notification" component={Notification} />
<AppStack.Screen name="Cart" component={Cart} />
</AppStack.Navigator>
Then, you could custom header component and import it in each your screen.
Sorry because my EL. hope help U.

Related

How do I define multiple types of navigation containers in the same component?

I am new to the world of react native, so I need a hand with structuring my navigation pages. I want my app to have instagram like structure - that is the main page should be createBottomTabNavigator From there I have few different pages, and I can switch between them easily. Here is my code:
export default const MyTabs = ({ currentUser, navigation }) => {
return (
<Tab.Navigator initialRouteName="Home">
<Tab.Screen
name="Home"
component={HomeScreen}
/>
<Tab.Screen
name="Network"
component={NetworkScreen}
/>
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{
tabBarIcon: ({ color, size }) => (
<Entypo name="map" color={color} size={26} />
),
}}
/>
</Tab.Navigator>
);
};
Then in my App.js file I call it like this:
return (
<Provider store={store}>
<NavigationContainer>
<MyTabs />
</NavigationContainer>
</Provider>
);
The above code works fine. The problem is when I want to perform more complicated navigation. For instance, in my home page I have list of objects and once, I click on these objects I want to navigate to a new page - with Stack navigator. How do I do so, and where should I define this page? Do I define it somehow inside my Home page, or inside my App.js page? In my mind it should be something like this:
<Stack.Screen
name="Details"
component={DetailScreen}
/>
I tried to put it inside App.js, something like this:
<Provider store={store}>
<NavigationContainer>
<MyTabs />
<Stack.Navigator>
<Stack.Screen
name="Meetup Details"
component={MeetupDetails}
options={{
headerTitle: "Meetup Details",
}}
/>
</Stack.Navigator>
</NavigationContainer>
</Provider>;
But ofc it didn't work.
Also I have another question - in my bottom tab I can navigate to My profile screen and that works fine. But I want to view other profiles as well. So my bottom tab is reserved for my own profile, but how do I define another Profile screen, which can go to other user's profile? Does it need to be with stack navigator? Does it again need to be defined in App.js? Is it ok to define two different navigators (bottom tab and stack) with the same page (Profile)?
Usually, when you have a bottom tab navigator, each tab will be a StackNavigator
export default const MyTabs = ({ currentUser, navigation }) => {
return (
<Tab.Navigator initialRouteName="Home">
<Tab.Screen
name="Home" // here this would be BottomTabRoutes.HOME
component={HomeNavigator}
/>
<Tab.Screen
name="Network" // here this would be BottomTabRoutes.NETWORK
component={NetworkNavigator}
/>
<Tab.Screen
name="Profile" // here this would be BottomTabRoutes.PROFILE
component={ProfileNavigator}
options={{
tabBarIcon: ({ color, size }) => (
<Entypo name="map" color={color} size={26} />
),
}}
/>
</Tab.Navigator>
);
};
And then your MeetupDetails screen will be contained inside the HomeNavigator which is a StackNavigator
export const HomeNavigator = () => {
return (
<Stack.Navigator
screenOptions={{
...
}}>
<Stack.Screen
name={HomeRoutes.HOME}
component={Home}
options={{
headerStyle: styles.header,
title: 'Home'
}}
/>
<Stack.Screen
name={HomeRoutes.MEETUP_DETAILS}
component={MeetupDetails}
options={{
headerStyle: styles.header,
title: 'Meetup Details'
}}
/>
</Stack.Navigator>
}
Also, I would suggest to store your routes names in an enum, in this way it will be easier and their possible route props in a tpye
Something like this:
export enum BottomTabRoutes {
HOME = 'Home',
NETWORK = 'Network',
PROFILE = 'Profile',
}
export type BottomTabRouteProps = {
[BottomTabRoutes.HOME]: {title: string}; // this is another parameter example
[BottomTabRoutes.PROFILE]: undefined;
[BottomTabRoutes.NETWORK]: undefined;
};
And the same for each Stack Navigator:
export enum HomeRoutes = {
HOME = 'Home',
MEETUP_DETAILS = 'Meetup Details',
}
export type HomeRouteProps = {
[HomeRoutes.HOME]: undefined
[HomeRoutes.MEETUP_DETAILS: { someParamName: SomeType } // this might be a parameter you'll send through navigation
}
// Therefore, your MeetupDetails screen will look something like this:
export const MeetupDetails = (props: StackScreenProps<HomeRouteProps, HomeRouteProps.MEETUP_DETAILS>,) => {
const navigationParam = props.route.params.someParamName
return (
<View>
...
</View>
)
}
EDIT
Regarding the Profile Screen, I believe you can have something like this:
export const MainNavigator = () => {
return (
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name={MainNavigatorRoutes.HOME} component={HomeNavigator} />
<Stack.Screen name={MainNavigatorRoutes.USER} component={UserNavigator} />
</Stack.Navigator>
);
};
export const UserNavigator = () => {
return (
<Stack.Navigator
screenOptions={({navigation}) => ({
...)}>
<Stack.Screen
name={UserNavigatorRoutes.SOME_OTHER_SCREEN}
component={UserSettings}
/>
<Stack.Screen
name={UserNavigatorRoutes.USER_PROFILE}
component={UserProfile}
/>
</Stack.Navigator>
);
};
export const HomeNavigator = () => {
return (
<Stack.Navigator screenOptions={{title: ''}} >
<Stack.Screen
name={HomeNavigatorRoutes.HOME_TABS}
component={HomeTabNavigator}
options={{
headerShown: false,
}}
/>
</Stack.Navigator>
);
};
export const HomeTabNavigator = () => (
<Stack.Navigator >
<Stack.Screen
name={HomeTabNavigatorRoutes.TAB_NAVIGATOR}
component={MyTabs}
options={{headerShown: false}}
/>
</Stack.Navigator>
Basically, the structure will look like this: MainNavigator = { HomeNavigator, UserNavigator }. In this way, each navigator will be separate and you should be able to navigate to any screen from UserNavigator even if you are in a screen from UserNavigator

Combine Stack, Drawer and Tab navigator in react-native

I've currently built, this navigation system but I feel like i've got it a bit wrong as in the order of things. I'm slowly developing issues such as not being able to hide the drawer navigator on certain screens of the tav navigator and etc..
Would someone be able to help me organise the navigation so that it makes a bit more sense?
const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();
const Tab = createMaterialTopTabNavigator();
const SwipeTabNavigator = () => {
return (
<Tab.Navigator
sceneContainerStyle={{
backgroundColor: theme['primaryColor'],
}}
tabBarOptions={{
style: {
display: 'none',
backgroundColor: '#08457e',
},
}}>
<Tab.Screen name="Component1" component={Component1} />
<Tab.Screen name="Component2" component={Component2} />
<Tab.Screen name="Component3" component={Component3} />
</Tab.Navigator>
);
};
const MainStackNavigator = () => {
return (
<Stack.Navigator
screenOptions={{
cardStyle: {backgroundColor: theme['primaryColor']},
header: () => <MenuComponent />,
}}>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="SwipeTabNavigator" component={SwipeTabNavigator} />
<Stack.Screen name="List" component={ListScreen} />
<Stack.Screen name="Detail" component={DetailScreen} />
</Stack.Navigator>
);
};
const App = () => {
return (
<Root>
<NavigationContainer>
<Drawer.Navigator
screenOptions={{swipeEnabled: false}}
drawerContent={(props) => <SidebarComponent {...props} />}
initialRouteName="Login">
<Drawer.Screen name="List" component={MainStackNavigator} />
</Drawer.Navigator>
</NavigationContainer>
</Root>
);
};
export default App;
After user logs in the navigation navigates to "SwipeTabNavigator", and I would like the drawer to be available at each screen :/
Any refactor help would be a blessing, thank you!

React Native Tab nested into Stack navigation v5 - best practice to update header

I am trying to understand what's the best approach to nested React Native Navigation v5.
I have a TabNavigation nested into a Stack.Navigator as follow:
const MainNavigation = () => {
return (
<SafeAreaProvider>
<NavigationContainer>
<StatusBar barStyle="light-content" />
<Stack.Navigator screenOptions={navStackOptions} >
<FirstListStack.Screen name="FirstListStack" component={TabNavigation} options={FirstNavOpt} />
<FirstListStack.Screen name="AnotherView" component={AnotherView} options={AnotherViewNavOpt} />
<SecondListStack.Screen name="SecondListStack" component={TabNavigation} options={SecondNavOpt} />
<ThirdListStack.Screen name="ThirdListStack" component={TabNavigation} options={ThirdNavOpt} />
</Stack.Navigator>
</NavigationContainer>
</SafeAreaProvider>
);
function TabNavigation() {
return (
<Tab.Navigator
initialRouteName="TabOne"
>
<Tab.Screen name="TabOne" component={TabOne} options={navTabOptions} />
<Tab.Screen name="TabTwo" component={TabTwo} options={navTabOptions} />
<Tab.Screen name="TabThree" component={TabThree} options={navTabOptions} />
</Tab.Navigator>
);
}
};
export default MainNavigation;
Now, when switching between the tabs, the stack navigation header does not get updated.
What's the best approach to access the state of the Stack Navigation and update its state? In particular to update the header buttons?
Let me know if my question is unclear.
Many thanks.
I found a solution, but I am not sure is a good practice.
I changed the logic of the Navigation moving the Tab Nav as parent and the Stack Nav as child.
const Tab = createBottomTabNavigator();
const FirstListStack = createStackNavigator();
const SecondListStack = createStackNavigator();
const ThirdListStack = createStackNavigator();
const MainNavigation = () => {
const navTabOptions = ({ route }) => ({
tabBarVisible: isTabBarVisible(route)
});
return (
<SafeAreaProvider>
<NavigationContainer>
<StatusBar barStyle="light-content" />
<Tab.Navigator>
<Tab.Screen name="First" component={FirstListStackScreen} options={navTabOptions} />
<Tab.Screen name="Second" component={SecondListStackScreen} options={navTabOptions} />
<Tab.Screen name="Third" component={ThirdListStackScreen} options={navTabOptions} />
</Tab.Navigator>
</NavigationContainer>
</SafeAreaProvider>
);
function isTabBarVisible(route) {
// Check here if the route.state is not undefined
// and based on the route return true or false
// to show or hide the tab bar
}
function FirstListStackScreen() {
return (
<FirstListStack.Navigator>
<FirstListStack.Screen name="First" component={} />
</FirstListStack.Navigator>
);
}
function SecondListStackScreen() {
return (
<SecondListStack.Navigator>
<SecondListStack.Screen name="Second" component={} />
</SecondListStack.Navigator>
);
}
function ThirdListStackScreen() {
return (
<ThirdListStack.Navigator>
<ThirdListStack.Screen name="Third" component={} />
</ThirdListStack.Navigator>
);
}
};
export default MainNavigation;
Please post any better solution to this.
Thanks

Is is possible to use navigation.toggleDrawer() in navigation options

In my navigation file , when I want to toggle drawer , get the following error :
TypeError: navigation.openDrawer is not a function.(In
'navigation.openDrawer()', 'navigation.openDrawer' is undefined)
This is my drawer:
const DrawerNavigator = () => {
return (
<Drawer.Navigator
initialRouteName="MYSHIFT"
>
<Drawer.Screen name="MYSHIFT" component={TopTabNavigator} />
</Drawer.Navigator>
)
}
And this is my container navigation :
const CareworkerNavigation = () => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Login"
component={LoginScreen}
options={{ headerShown: false }} />
<Stack.Screen
name="Main"
options={({ navigation }) => {
return {
headerLeft: () => <Button title="LEFT BUTTON" onPress={() => {
navigation.toggleDrawer(); // <--- this line throws an error
}} />
}
}}
component={DrawerNavigator} />
</Stack.Navigator>
</NavigationContainer>
)
}
export default CareworkerNavigation
Why I can not use navigation.toggleDrawer() in navigation options?
Is is possible to remove this problem ?
If you check the React Navigation docs, "You will need to make the drawer navigator the parent of any navigator where the drawer should be rendered on top of its UI."
React Navigation docs reference
To answer your question : Yes , it is possible.
And here you have a working example:
import React from 'react'
import { Button, View } from 'react-native'
import { NavigationContainer } from '#react-navigation/native'
import { createDrawerNavigator } from '#react-navigation/drawer'
import { createStackNavigator } from '#react-navigation/stack'
const Feed = () => <View />
const Notifications = () => <View />
const Profile = () => <View />
const FeedStack = createStackNavigator()
const Home = ({ navigation }) => (
<FeedStack.Navigator>
<FeedStack.Screen
name="Feed"
component={Feed}
options={props => {
const { toggleDrawer } = props.navigation // <-- drawer's navigation (not from stack)
return {
headerLeft: () => <Button title="LEFT BUTTON" onPress={toggleDrawer} />
}
}}
/>
</FeedStack.Navigator>
)
const Drawer = createDrawerNavigator()
export default props => {
return (
<NavigationContainer>
<Drawer.Navigator initialRouteName="Feed">
<Drawer.Screen
name="Feed"
component={Home}
options={{ drawerLabel: 'Home' }}
/>
<Drawer.Screen
name="Notifications"
component={Notifications}
options={{ drawerLabel: 'Updates' }}
/>
<Drawer.Screen
name="Profile"
component={Profile}
options={{ drawerLabel: 'Profile' }}
/>
</Drawer.Navigator>
</NavigationContainer>
)
}
While constructing navigation at options, you refer to the navigation of the stack, what cant perform draw actions, try to construct it on header itself
<Stack.Screen
name="Main"
options={() => {
return {
headerLeft: (navigation) => <Button title="LEFT BUTTON" onPress={() => {
navigation.toggleDrawer(); // <--- this line throws an error
}} />
}
}}
component={DrawerNavigator} />
https://github.com/react-navigation/react-navigation/issues/55

React navigation 5 - header is not shown

Trying to update my app to react navigation 5 and been confronting some issues.
First of all, the header does not show up. Snips from the code:
[from App.js]
const Tab = createBottomTabNavigator();
function App() {
return (
<NavigationContainer>
<Tab.Navigator >
<Tab.Screen name="Home" component={HomeScreen} options={{ title:'some title' }}/>
<Tab.Screen name="Upload" component={UploadScreen} />
<Tab.Screen name="Find" component={FindScreen} />
</Tab.Navigator>
</NavigationContainer>
);
}
export default App;
and the style of the current screen:
<View style={{flex:1, flexDirection:'column',justifyContent:'space-between'}}>
Here is a screenshot of the app on an Android emulator (and it looks the same on my phone):
As you can see, the header is not shown, the tab navgiation does not right, and so are the buttons (something changed about their background). I did not change anything in the app besides upgrading to react-navigation 5..
Thanks for the help!
Tab navigators do not have header support. You have to wrap your tab navigator inside a stack navigator.
import { createStackNavigator } from "#react-navigation/stack";
// ... other imports
export const App = () => {
return (
<NavigationContainer>
<StackNavigator />
</NavigationContainer>
);
}
const Stack = createStackNavigator();
const StackNavigator = () => {
return (
<Stack.Navigator>
<Stack.Screen name="Tabs" component={TabNavigator} />
</Stack.Navigator>
);
}
const Tab = createTabNavigator();
const TabNavigator = () => {
return (
<Tab.Navigator >
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Upload" component={UploadScreen} />
<Tab.Screen name="Find" component={FindScreen} />
</Tab.Navigator>
);
}