Hiding tab bar on screens inside a nested stack navigator - react-native

I want to hide the tab bar on certain screens inside a nested stack navigator. I have a bottom tab bar, using a CUSTOM tab bar, set up like so:
const MainAppBottomTabs = createBottomTabNavigator<BottomTabParamList>();
const MainAppBottomTabsNavigator = (): JSX.Element => {
return (
<MainAppBottomTabs.Navigator
initialRouteName="ListScreen"
screenOptions={{
headerShown: false
}}
tabBar={(props: BottomTabBarProps) => (
<TabBar
state={props.state}
navigation={props.navigation}
descriptors={props.descriptors}
insets={props.insets}
/> // this is my custom tab bar
)}>
<MainAppBottomTabs.Screen
name="ListScreen"
component={ListScreen}
/>
<MainAppBottomTabs.Screen
name="ProductsScreen"
component={ProductsScreen}
/>
<MainAppBottomTabs.Screen
name="DetailsStackNavigator"
component={DetailsStackNavigator}
/>
</MainAppBottomTabs.Navigator>
);
};
Here's the nested DetailsStackNavigator:
const DetailsStack = createNativeStackNavigator();
const DetailsStackNavigator = (): JSX.Element => {
return (
<DetailsStack.Navigator
initialRouteName="UsersScreen"
screenOptions={{
headerShown: false
}}>
<DetailsStack.Screen
name="UsersScreen"
component={UsersScreen}
options={{ animation: 'none' }}
/>
<DetailsStack.Screen
name="OptionsScreen"
component={OptionsScreen}
options={{ animation: 'none' }}
/>
<DetailsStack.Screen name="OptionsDetailsScreen" component={OptionsDetailsScreen} />
<DetailsStack.Screen name="UsersDetailsScreen" component={UsersDetailsScreen} />
</DetailsStack.Navigator>
);
};
I want to hide the tab bar on the OptionsDetailsScreen and UsersDetailsScreen inside of the DetailsStackNavigator. I have tried the following which DOES hide the tab bar, but it cuts off the screens at the bottom where the tab bar would be:
const MainAppBottomTabs = createBottomTabNavigator<BottomTabParamList>();
const MainAppBottomTabsNavigator = (): JSX.Element => {
const getTabBarVisibility = (route) => {
const routeName = getFocusedRouteNameFromRoute(route);
const hideOnScreens = ['OptionsDetailsScreen', 'UsersDetailsScreen'];
return hideOnScreens.indexOf(routeName) <= -1 ? 'flex' : 'none';
};
return (
<MainAppBottomTabs.Navigator
// rest of code from above
<MainAppBottomTabs.Screen
name="DetailsStackNavigator"
component={DetailsStackNavigator}
options={({ route }) => ({
tabBarStyle: { display: getTabBarVisibility(route) }
})}
/>
</MainAppBottomTabs.Navigator>
);
};
and then in my custom tab bar:
const TabBar = (screenProps: ScreenProps): JSX.Element => {
const focusedOptions = screenProps.descriptors[screenProps.state.routes[screenProps.state.index].key].options;
if (focusedOptions?.tabBarStyle?.display === 'none') {
return null;
}
return <View>{RenderTabs(screenProps)}</View>;
};
Not sure what else to try. Seems to be way too difficult for what I would assume would be a pretty common scenario.

Related

Two tabs (Tab.Navigator) using one component to display lists, only data is different, Back button for the details page works wrong

when I tried to refactor the mobile app of 'Notes' from ‘JavaScript Everywhere’ book, a problem
The structure is like this:
RootNavigator(Stack.Navigator)
---AuthLoading(Screen)
---Auth(Stack.Navigator)
--SignIn(Screen)
--SignUp(Screen)
---App(Tab.Navigator)
--FeedStack(Stack.Navigator)
-FeedScreen(Screen)
-NoteScreen(Screen)
--MyNotesStack(Stack.Navigator)
-MyNotesScreen(Screen)
-NoteScreen(Screen)
--FavoritesStack(Stack.Navigator)
-FavoritesScreen(Screen)
-NoteScreen(Screen)
--SettingsStack(Stack.Navigator)
When a user login, the default tab is ‘Feed’ which goes to ‘FeedStack’, and FeedScreen list all the notes created by all the users, click one item of the notes, it goes to a NoteScreen, display the details of that ‘Note’, everything goes well.
When the user choose ‘MyNotes’ tab which goes to ‘MyNoteStack’, it list the notes created by the current user, click one of the ‘note’ item, it goes to NoteScreen, display the details of that ‘note’. However, now, the default focus of the Tab.Navigator is ‘Feed’, and when I click back button in the NoteScreen, it goes back to ‘FeedStack’ which displays all the notes. It is weird!
I can’t understand why I go to the NoteScreen from ‘MyNotes’, but back button leads it to ‘Feed’, how to fix this problem?
And the code is as follows.
In index.js (RootNavigator)
const Stack = createNativeStackNavigator();
const Tab = createBottomTabNavigator();
const AuthStack = createNativeStackNavigator();
const feedStack = createNativeStackNavigator();
const myNotesStack = createNativeStackNavigator();
const favoritesStack = createNativeStackNavigator();
const settingsStack = createNativeStackNavigator();
function FeedStack () {
return (
<feedStack.Navigator
screenOptions={
{headerShown:false}
}
>
<feedStack.Screen name="FeedScreen" component={FeedScreen} />
<feedStack.Screen name="NoteScreen" component={NoteScreen} options={{headerShown:true}}/>
</feedStack.Navigator>
);
}
function MyNotesStack () {
return (
<myNotesStack.Navigator
screenOptions={
{headerShown:false}
}
>
<myNotesStack.Screen name="MyNotesScreen" component={MyNotesScreen} />
<myNotesStack.Screen name="Note" component={NoteScreen} options={{headerShown:true}} />
</myNotesStack.Navigator>
);
}
function FavoritesStack () {
return (
<favoritesStack.Navigator
screenOptions={
{headerShown:false}
}
>
<favoritesStack.Screen name="FavoritesScreen" component={FavoritesScreen} />
<favoritesStack.Screen name="Note" component={NoteScreen} options={{headerShown:true}}/>
</favoritesStack.Navigator>
);
}
function SettingsStack () {
return (
<settingsStack.Navigator
screenOptions={
{headerShown:false}
}
>
<settingsStack.Screen name="SettingsScreen" component={SettingsScreen} />
</settingsStack.Navigator>
);
}
const TabNavigator = () => {
return (
<Tab.Navigator
initialRouteName="MyNotes"
activeColor='#f0f'
inactiveColor='#555'
barStyle={{
backgroundColor:'#999'
}}
screenOptions={({route}) => ({
headerShown: false,
tabBarIcon:({focused, size, color}) => {
let iconName;
if( route.name === 'FeedStack') {
iconName = 'home';
} else if (route.name === 'MyNotesStack') {
iconName = 'bed';
} else if (route.name === 'FavoritesStack') {
iconName = 'star'
} else {
iconName = 'spa'
}
color = focused ? '#f0f' : "#555";
size = focused ? 24 : 20;
return <FontAwesome5 name={iconName} size={size} color={color}/>;
},
})}
>
<Tab.Screen name='FeedStack' component={FeedStack} options={{headerShown: false}} />
<Tab.Screen name='MyNotesStack' component={MyNotesStack} options={{headerShown: false}} />
<Tab.Screen name='FavoritesStack' component={FavoritesStack} options={{headerShown: false}}/>
<Tab.Screen name='SettingsStack' component={SettingsStack} options={{headerShown: false}} />
</Tab.Navigator>
);
};
const Auth= () => {
return (
<AuthStack.Navigator
screenOptions={{headerShown:false}}
>
<AuthStack.Screen name='signIn' component={SignIn}></AuthStack.Screen>
</AuthStack.Navigator>
);
};
const RootNavigator = () => {
return (
<Stack.Navigator initialRouteName='AuthLoading'>
<Stack.Screen name='AuthLoading'
component={AuthLoading}
options={{title:'AuthLoading'}}
>
</Stack.Screen>
<Stack.Screen name='Auth'
component={Auth}
options={{
title: 'Auth',
headerStyle: {
backgroundColor: '#f4511e',
},
headerBackVisible: false,
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}
>
</Stack.Screen>
<Stack.Screen name='App'
component={TabNavigator}
options={{
title: 'App',
headerStyle: { backgroundColor: '#f4511e'},
headerBackVisible:false,
headerTintColor: '#fff',
headerTitleStyle: {fontWeight: 'bold'},
}}
>
</Stack.Screen>
</Stack.Navigator>
);
};
export default RootNavigator;
In FeedScreen.js:
const FeedScreen = () => {
const { data, loading, error } = useQuery(GET_NOTES);
// if the data is loading, our app will display a loading indicator
if(loading)
return <Loading />;
if(error)
return <Text>Error loading notes.</Text>;
// if the query is successful and there are notes, return the feed of notes
return (
<NoteFeed notes={data.notes} />
);
};
In MyNotesScreen.js
const MyNotesScreen = () => {
const { data, loading, error } = useQuery(GET_MY_NOTES);
// if the data is loading, our app will display a loading indicator
if(loading)
return <Loading />;
if(error)
return <Text>Error loading MyNotes.</Text>;
// if the query is successful and there are notes, return the feed of notes
// else if the query is successful and there aren't notes, display a message
if(data.me.notes.length !== 0) {
return <NoteFeed notes={data.me.notes} />;
} else {
return <Text>No notes yet</Text>;
}
// If I don't use <NoteFeed> here, for example, show a button then go to <NoteScreen> it is ok.
// return (
// <View style={styles.container}>
// <Text>My Notes Screen</Text>
// use self-defined button
// <JereButton
// onPress={() => navigation.navigate('Note',{id:'63b94da5ccf7f90023169c3d'})}
// title="Go to a note"
// color={"#882"}
// />
// </View>
// );
};
In NoteFeed.js
const NoteFeed = props => {
// only screen components receive navigation prop automatically!
// if you wish to access the navigation prop in any of your components, you may use the useNavigation hook.
const navigation = useNavigation();
return (
<View style={styles.container}>
<FlatList
data={props.notes}
keyExtractor = {({id}) => id.toString()}
renderItem = {({item}) => (
<Pressable style={styles.feedview}
onPress={() => navigation.navigate('NoteScreen',{id: item.id})}
>
<Text style={styles.text}>{item.content}</Text>
</Pressable>
)}
/>
</View>
);
};
In NoteScreen.js
const NoteScreen = ({navigation, route}) => {
const {id} = route.params;
const { data, loading, error } = useQuery(GET_NOTE, {variables:{id}});
// if the data is loading, our app will display a loading indicator
if(loading)
return <Loading />;
if(error)
return <Text>Error Note not found.</Text>;
return (
<Note note={data.note} />
);
};
Thank you for your help.
I tried to replace useNavigation() to props solution, the issue is the same. Then I tried to do not use in 'MyNotes' to show the ‘note’, it is OK, but it doesn’t comply with the design.

Nested Navigation Stack does not find navigator

I am want to add a secondary Navigation so that a subset of screens have access to providers, as opposed to the whole stack being wrapped by providers they do not need access to. However, I think there is something wrong with my nested setup because I get an error when clicking a button that would navigate to the substack:
ERROR The action 'NAVIGATE' with payload {"name":"VehicleDetailInspections","params":{"vehicleId":27541}} was not handled by any navigator.
Do you have a screen named 'VehicleDetailInspections'?
If you're trying to navigate to a screen in a nested navigator, see https://reactnavigation.org/docs/nesting-navigators#navigating-to-a-screen-in-a-nested-navigator.
The link to this only shows tabs & drawers, which we do not have.
My setup is:
HOMESTACK
const HomeStack = createNativeStackNavigator<HomeStackParams>();
export function HomeNavigator(): JSX.Element {
const styles = useInspectionStyles();
return (
<VehicleDataProvider>
<HomeStack.Navigator
screenOptions={{
headerStyle: styles.headerStyles,
headerBackVisible: false,
headerTitle: '',
headerLeft: () => <GoBack />,
}}
>
<HomeStack.Group
screenOptions={{
headerShadowVisible: false,
headerStyle: [styles.headerStyles],
}}
>
<HomeStack.Screen
name={VEHICLE_DETAIL}
component={VEHICLE_DETAIL.component}
/>
</HomeStack.Group>
/*
// I have tried both of these options:
<HomeStack.Group>
{() => <InspectionNavigator />}
</HomeStack.Group>
// or
<HomeStack.Screen
name={'InspectionSubStack'}
component={InspectionNavigator}
/>
*/
</HomeStack.Navigator>
</VehicleDataProvider>
);
}
In the above HomeStack, in the VEHICLE_DETAIL screen, there is a button which when clicked navigates to VEHICLE_DETAIL_INSPECTIONS which is part of the InspectionStack below.
const navigation = useNavigation<NavigationProp<InspectionStackParams>>();
...
onPress={() =>
navigation.navigate('VEHICLE_DETAIL_INSPECTIONS', {
vehicleId: vehicle.id,
})
}
INSPECTION substack:
const InspectionStack = createNativeStackNavigator<InspectionStackParams>();
export function InspectionNavigator(): JSX.Element {
const styles = useInspectionStyles();
return (
<InspectionsDataProvider>
<InspectionProgresssProvider>
<InspectionStack.Navigator
screenOptions={{
headerStyle: styles.headerStyles,
headerBackVisible: false,
headerTitle: '',
headerLeft: () => <GoBack />,
}}
>
<InspectionStack.Screen
name={VEHICLE_DETAIL_INSPECTIONS}
component={
VEHICLE_DETAIL_INSPECTIONS.component
}
/>
<InspectionStack.Screen
name={VEHICLE_INSPECTIONS_SECTION].name}
component={
VEHICLE_INSPECTIONS_SECTION.component
}
/>
<InspectionStack.Screen
name={VEHICLE_INSPECTIONS_STEP}
component={VEHICLE_INSPECTIONS_STEP.component}
/>
</InspectionStack.Navigator>
</InspectionProgresssProvider>
</InspectionsDataProvider>
);
}
What do I need to do to get the InspectionNavigator to be found by the navigate() from HomeStack?

React Native pass params through tabs.screen

how can I pass params or an object to a Tabs-Screen when clicking on that tab in React Nativ? This is the code of a bottom Tab Navigation on one page of the app.
const Tab = createBottomTabNavigator();
function MainContainer({ route, navigation }) {
//function MainContainer({ }) {
// Load the icon font before using it
const [fontsLoaded] = useFonts({ IcoMoon: require('../../assets/icomoon/zepra_icons.ttf') });
const { data } = route.params;
console.log('data:');
console.log(data);
if (!fontsLoaded) {
return <AppLoading />;
}
return (
<Tab.Navigator
initialRouteName={projectsName}
screenOptions={({ route }) => ({
headerShown: false,
tabBarIcon: ({ focused, color, size }) => {
let iconName;
let rn = route.name;
if (rn === projectsName) {
iconName = focused ? 'PROJEKTE_ALLE' : 'PROJEKTE_ALLE';
} else if (rn === waermeschutzName) {
iconName = focused ? 'HAUS_3' : 'HAUS_3';
} else if (rn === begehungenName) {
iconName = focused ? 'NOTIZ_ERSTELLEN' : 'NOTIZ_ERSTELLEN';
}
return <Icon name={iconName} size={43} color={color} />;
},
'tabBarActiveTintColor': '#4283b1',
'tabBarInactiveTintColor': '#5db8bd',
'tabBarStyle':{ 'paddingTop':4, 'height':90 },
'tabBarLabelStyle':{ 'paddingTop':3, 'fontSize':13 }
})}>
<Tab.Screen name={projectsName} component={ProjectsScreen} />
<Tab.Screen name={waermeschutzName} component={WaermeschutzScreen} />
<Tab.Screen name={begehungenName} component={BegehungenScreen} />
</Tab.Navigator>
);
}
export default MainContainer;
I have tryed several ways but could not get it to work. Does anyone have a working example or cane someone help me with my code??
Thank you
You could use the initialParams prop for the Tab.Screen components in order to pass the initial params to each tab.
Here is an example on how to do this for your first tab screen.
<Tab.Screen name={projectsName} component={ProjectsScreen} initialParams={{ data }} />

Making react native TopNavigation bar absolute

So what i want to achieve is a navigation bar that has a background color of blue and have a 60% opacity so that it can show the image behind it. I can only achieve this by making the header's position an absolute but it wont let me click the back button.
here is my code stacknavigator
const UnregisteredNavigator = () => {
return (
<Stack.Navigator
screenOptions={({ navigation,route }) => ({
headerStyle: {
height: 60
},
header: ({}) => {
return (
<ThemedTopNavigation navigation={navigation} route={route} />
);
},
})}
>
<Stack.Screen name="LandingScreen" component={LandingScreen} options={{headerShown:false}} />
<Stack.Screen name="Login" component={SignInScreen} />
</Stack.Navigator>
);
};
and here is my ThemedTopNavigation code
const TopNavigationBar = ({ navigation: { goBack },route,eva }) => {
const themeContext = React.useContext(ThemeContext);
console.log("styled", eva);
const [menuVisible, setMenuVisible] = React.useState(false);
const [checked, setChecked] = React.useState(false);
const onCheckedChange = (isChecked) => {
themeContext.toggleTheme()
setChecked(isChecked);
};
const backFunction = () => {
alert("back");
// goBack()
};
const renderBackAction = () => (
<TopNavigationAction style={{backgroundColor:"red",zIndex: 1, position:'relative'}} icon={BackIcon} onPress={backFunction}/>
);
return (
<Layout style={eva.style.navLayout}>
<TopNavigation
style={[eva.style.navContainer, styles]}
alignment='center'
title={()=>(<Button>CLICK</Button>)}
accessoryLeft={renderBackAction}
/>
</Layout>
);
};
I am using UI kitten. anyone can help me?

Statically setting stack navigator's header title

Suppose I have 2 navigators as follow:
const StackNavigator = () => {
const theme = useTheme();
return (
<Stack.Navigator
initialRouteName="BottomTabs"
headerMode="screen"
screenOptions={{
header: Header,
}}>
<Stack.Screen
name="BottomTabs"
component={BottomTabs}
options={({route, navigation}) => {
console.log('get bottom tab title', route, navigation)
const routeName = route.state
? route.state.routes[route.state.index].name
: 'NOTITLE';
return {headerTitle: routeName};
}}
/>
</Stack.Navigator>
);
};
This Stack navigator loads BottomTabs which is another navigator:
const BottomTabs = props => {
const theme = useTheme();
const tabBarColor = theme.dark
? overlay(6, theme.colors.surface)
: theme.colors.surface;
return (
<Tab.Navigator
initialRouteName="TaskList"
shifting={true}
activeColor={theme.colors.primary}
inactiveColor={color(theme.colors.text)
.alpha(0.6)
.rgb()
.string()}
backBehavior={'initialRoute'}
sceneAnimationEnabled={true}>
<Tab.Screen
name="TaskList"
component={TaskListScreen}
options={{
tabBarIcon: ({focused, color}) => (
<FeatherIcons color={color} name={'check-square'} size={23} />
),
tabBarLabel: 'InboxLabel',
tabBarColor,
title: 'Inbo title',
}}
/>
<Tab.Screen
name="Settings"
component={SettingsScreen}
options={{
tabBarIcon: ({focused, color}) => (
<FeatherIcons color={color} name={'settings'} size={23} />
),
tabBarLabel: 'SeetingsLabel',
tabBarColor,
title: 'Settings title',
}}
/>
</Tab.Navigator>
);
};
I want to change Stack header title based on the screen loaded from BottomTabs. trying to pass the title option to individual screen in BottomTabs didn't work.
How can I change Stack navigator's title based on the screen loaded from the child?
You can customize headerTitle like this:
<Stack.Screen
name="BottomTabs"
component={BottomTabs}
options={({route}) => {
let title;
const routeName = route.state
? route.state.routes[route.state.index].name
: route.params && route.params.screen
? route.params.screen
: 'TaskList';
switch (routeName) {
case 'TaskList':
title = 'Tasks screen';
break;
case 'Settings':
title = 'Settings screen';
break;
default:
return routeName;
}
return {headerTitle: title};
}}
/>
important note: route.state is undefined before any navigate. after that, stack creats it's on navigation state and your screen name prop is available.
Reference