Im trying to add nested navigation to my react native project - reactjs

Im following a react native app tutorial that has stack navigation only but I'm trying to also add nested navigation so I have a tab bar at the bottom that leads to additional pages I'm building but can't seem to figure out how to implement in my current code. Here is my code so far not sure if i'm on the right track or way off
{
DashBoard: DashBoardScreen,
Articles: ArticlesScreen,
Article: ArticleScreen,
Search: SearchScreen
},
{
initialRouteName: 'DashBoard',
defaultNavigationOptions: ({navigation}) => ({
headerStyle, headerTitleStyle,
headerRight: (<SearchBtn onPress={() => navigation.navigate('Search')}/>)
})
}
);
const Tab = createBottomTabNavigator();
const TabNavigator = () => (
<Tab.Navigator>
<Tab.Screen name="News" component={DashBoard}/>
<Tab.Screen name="Game" component={Game}/>
</Tab.Navigator>
)```

Here's how you can achieve this:
const BottomTab = createBottomTabNavigator();
const Stack = createStackNavigator();
const AppStack = () => {
return (
<BottomTab.Navigator>
<BottomTab.Screen name="Screen1" component={Screen1} />
<BottomTab.Screen name="Screen2" component={Screen2} />
</BottomTab.Navigator>
);
};
const MainStack = () => {
return (
<Stack.Navigator headerMode="none">
<Stack.Screen name="App" component={AppStack} />
<Stack.Screen name="Auth" component={AuthStack} />
</Stack.Navigator>
);
};

Related

React Native navigate between stacks - drawer to stack

I have a React Native app that I am adding navigation to. I have two different stacks I want to navigate between. I have searched SO and came up with the two versions which I have in my below code. The setup is that I have a main stack navigator which has a drawer stack and a normal stack (Auth) that I'd like to be able to go between. The main things are redirecting on login and logout from the header of the Drawer stack.
Below is my code, I am looking for help to figure out how to enable the mentioned navigation that I need the app to do. In theory, this is a common thing so I am pretty sure it should be supported and documented though I can't seem to find anything documented on it.
Error message
The action 'NAVIGATE' with payload {"name":"Auth","params":{}} was not handled by any navigator.
Do you have a screen named 'Auth'?
HeaderRight
export default function HeaderRight ({ navigation }) {
const { removeAuth } = useContext(AuthContext);
const logout = () => {
removeAuth()
// Note both are uncommented for sharing and are not actually meant to be when running the app
navigation.navigate('Auth', { screen: 'Login' }); // { screen: 'Login' }
navigation.navigate(
'Auth',
{},
NavigationActions.navigate({
routeName: 'Login'
})
)
}
return (
<View style={styles.header}>
<Icon name='logout' onPress={logout} size={28} style={styles.icon} color="white" />
</View>
)
}
Root navigation
<Stack.Navigator
initialRouteName={"Splash"}
screenOptions={{}}
component={SplashScreen}
>
{ checkAuth ?
<Stack.Screen name="Drawer" component={DrawerStack} options={({ navigation }) => ({
title: 'My App',
headerRight: () => (
<HeaderRight navigation={navigation}/>
),
headerTitleAlign: 'center',
headerTintColor: 'white',
headerStyle: {
backgroundColor: '#5742f5'
},
})} />
:
<Stack.Screen name="Auth" component={AuthStack} options={{
headerShown: false
}}/>
}
</Stack.Navigator>
drawer stack
export const DrawerStack = (props) => {
return (
<Drawer.Navigator
initialRouteName="Lists"
>
<Drawer.Screen
name="Conversations"
options={{
title: props.title,
}}
component={ListScreen}
/>
<Drawer.Screen
name="Room"
options={{
drawerLabel: () => null,
title: null,
drawerIcon: () => null,
}}
component={RoomScreen}
/>
</Drawer.Navigator>
);
};
auth stack
export const AuthStack = () => {
return (
<Stack.Navigator
initialRouteName={'Login'}
>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Forgot" component={ForgotScreen} />
</Stack.Navigator>
);
};
Edit
I think I have found the potential cause for this but I have no clue about the solution to it.
I have this in my root navigator (same as above)
{ checkAuth ?
<Stack.Screen name="Drawer" component={DrawerStack} options={({ navigation }) => ({
title: 'My App',
headerLeft: () => (
<HeaderLeft />
),
headerRight: () => (
<HeaderRight navigation={navigation}/>
),
})} />
:
<Stack.Screen name="Auth" component={AuthStack} options={{
headerShown: false
}}/>
}
This means that the stack/screen for Auth is only being rendered/exists when the drawer stack isn't shown. How can I refactor my root navigator to fix this?
I am thinking I need to somehow force the checkAuth function to re-render based on different actions taken or to watch a certain local storage.
This is the code for checkAuth
const checkAuth = () => {
try {
const authData = globalStorage.getString('accessToken')
if(authData !== null && authData !== undefined) {
setAuth(authData)
return true
} else {
return false
}
} catch(e) {
console.error(e)
return false
}
}
Context on what globalStorage is
import { MMKV } from 'react-native-mmkv'
export const globalStorage = new MMKV()

React Native Navigation 5.0 bottomTabs - how to change the font size?

is there a way to change the font size of the two "name" props below in React Native Navigation 5.0?
const Tab = createBottomTabNavigator();
export const Nav = () => {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Game" component={HomeStackScreen} />
<Tab.Screen name="Map" component={SettingsStackScreen} />
</Tab.Navigator>
</NavigationContainer>
);
};
resolved in issue #21. all good :)
https://github.com/ptomasroos/react-native-tab-navigator/issues/21
Add tabBoarOptions to your code like this.
const Tab = createBottomTabNavigator();
export const Nav = () => {
return (
<NavigationContainer>
<Tab.Navigator
labelStyle: {
fontSize: 10,
fontFamily:'GothamBook'
},>
<Tab.Screen name="Game" component={HomeStackScreen} />
<Tab.Screen name="Map" component={SettingsStackScreen} />
</Tab.Navigator>
</NavigationContainer>
);
};

Nesting navigators in React Navigation

I'm making an app in which I have more than one type of navigation, but I'm not sure how to implement it until now, my apps only had one type of navigation. I tried to read the react-navigation docs that talked about this particular implementation, but I couldn't do it. Can someone help me?
So you guys understand, I'm trying to implement a Stack navigator inside a Tab navigator so that when the user clicks on a point in a map, a details page shows up to him.
Here's what I tried to do
import React from 'react'
import { NavigationContainer } from '#react-navigation/native'
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs'
import { createStackNavigator } from '#react-navigation/stack'
import { Map } from '../pages/Map'
import { Detail } from '../pages/Detail'
import FiIcon from '#expo/vector-icons/Feather'
const Tab = createBottomTabNavigator()
const Stack = createStackNavigator()
export const UsersRoutes = () => {
return (
<>
<NavigationContainer>
<Tab.Navigator
initialRouteName='Mapa'
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName;
if (route.name === 'Mapa') {
iconName = 'map'
} else if (route.name === 'Pesquisar') {
iconName = 'search'
} else {
iconName = 'user'
}
return <FiIcon name={iconName} size={size} color={color} />;
},
})}
tabBarOptions={{
activeTintColor: 'black',
inactiveTintColor: 'gray',
}}
>
<Tab.Screen name='Mapa' component={Map} />
<Tab.Screen name='Pesquisar' component={() => <Text>Search</Text>} />
<Tab.Screen name='Perfil' component={() => <Text>Profile</Text>} />
</Tab.Navigator>
</NavigationContainer>
<NavigationContainer>
<Stack.Navigator headerMode='none'>
<Stack.Screen name='Mapa' component={Map} />
<Stack.Screen name='Detalhes' component={Detail} />
</Stack.Navigator>
</NavigationContainer>
</>
)
}
What you can do is:
const MapaStack = () => (
<Stack.Navigator>
<Stack.Screen name="Mapa" component={MapaScreen} />
<Stack.Screen name="AnyScreen" componnent="AnyScreen" />
</Stack.Navigator>
);
const Tabs = () => (
<Tab.Navigator>
<Tab.Screen name="Mapa" component={MapaStack} />
<Tab.Screen name="Pesquisar" component={PesquisarScreen} />
</Tab.Navigator>
);
// you could use Tabs directly instead of AppStack but there can be more requirements...
const AppStack = () => (
<Stack.Navigator>
<Stack.Screen name="Tabs" component="Tabs" />
</Stack.Navigator>
);
Basically, you can pass Navigators instead of Screens as component prop. And you need to use AppStack inside NavigationContainer. You need to think carefully before implementing the navigators because hierarchy is important as it states here.

Unable to navigate to drawer stack in react navigation 5

I have an app that has a bottom tab navigator inside a drawer navigator as my Home Screen and I also have a stack navigator comprising of my auth screens (including login screen).
I am unable to navigate from my login screen to my Home stack screen after a successful login.
I get the error - the action navigate with payload was not handled by any navigator.
Please see my code. How can I resolve this
Navigation file - Navigation.js
const MainTabScreen = () => (
<Tab.Navigator
initialRouteName="Main"
activeColor="#fff"
>
<Tab.Screen
name="Main"
component={Main}
options={{
tabBarLabel: 'Home',
tabBarColor: "#009387",
tabBarIcon: ({ color }) => (
<Icon name="md-home" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{
tabBarLabel: 'Profile',
tabBarColor: "#694fad",
tabBarIcon: ({ color }) => (
<Icon name="md-contact" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="Explore"
component={ExploreScreen}
options={{
tabBarLabel: 'Account',
tabBarColor: "#d02860",
tabBarIcon: ({ color }) => (
<Icon name="md-planet" color={color} size={26} />
),
}}
/>
</Tab.Navigator>
)
// HOME STACK SCREEN
const DrawerScreen = () => (
<Drawer.Navigator drawerContent={props => <DrawerContent {...props} /> } >
<Drawer.Screen name='MainTab' component={MainTabScreen} />
</Drawer.Navigator>
);
const AuthStack = createStackNavigator();
const AuthStackScreen = ({navigation}) => (
<AuthStack.Navigator headerMode='none'>
<AuthStack.Screen name="SplashScreen" component={SplashScreen} />
<AuthStack.Screen name="SignInScreen" component={SignInScreen} /> // LOGIN SCREEN
<AuthStack.Screen name="SignUpScreen" component={SignUpScreen} />
</AuthStack.Navigator>
);
const RootStack = createStackNavigator();
const RootStackScreen = () => {
const [isLoading, setIsLoading] = React.useState(true);
const [user, setUser] = React.useState(null);
React.useEffect(() => {
// Fetch the token from storage then navigate to our appropriate place
const bootstrapAsync = async () => {
let userToken;
try {
userToken = await AsyncStorage.getItem('FBIdToken');
console.log("toen", userToken)
if(userToken !== null) {
setIsLoading(!isLoading);
setUser(userToken);
} else {
setIsLoading(!isLoading);
}
} catch (e) {
console.log(error)
}
// After restoring token, we may need to validate it in production apps
};
bootstrapAsync();
}, []);
return (
<RootStack.Navigator
headerMode="none"
screenOptions={{ animationEnabled: false }}
mode="modal"
>
{isLoading ? (
<RootStack.Screen name="Loading" component={LoadingScreen} />
) : user ? (
<RootStack.Screen name="DrawerScreen" component={DrawerScreen} /> //HOME STACK ) : (
<RootStack.Screen name="AuthStackScreen" component={AuthStackScreen} />
)}
</RootStack.Navigator>
);
};
export default RootStackScreen;
App.js
<Provider store={store}>
<NavigationContainer ref={navigationRef}>
<RootStackScreen />
</NavigationContainer>
</Provider>
Login.js
loginHandle = () => {
this.props.navigation.navigate("DrawerScreen")
};
I suspect that in your RootStackScreen component, your condition isLoading is true so the route to your DrawerScreen does not exists when you navigate. Try removing the condition to be sure. Then if this isthe reason, yu could try applying your condition to the component you pass to your DrawerScreen instead, something like :
<RootStack.Screen name="DrawerScreen" component={isLoading ? LoadingScreen : DrawerScreen} />

Override Header Title React Navigation V5

I have created a nested tab navigator in my stack navigator as follows:
const Stack = createStackNavigator()
const Tab = createBottomTabNavigator();
function TabNav () {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen}></Tab.Screen>
<Tab.Screen name="Category" component={CategoryScreen}></Tab.Screen>
<Tab.Screen name="Settings" component={SettingsScreen}></Tab.Screen>
</Tab.Navigator>
)
}
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Tab" component={TabNav}></Stack.Screen>
<Stack.Screen name="Category" component={CategoryScreen}>
</Stack.Screen>
<Stack.Screen
name="SpecialBeerScreen" component= {SpecialBeerScreen}>
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
)
}
However, the header now looks like Tab for each of the screens. How do I override this Header for each of the screens with a custom text, such as Home, Category etc.
Update: This is still valid in react-navigation v6
Use getFocusedRouteNameFromRoute as outlined in the react-navigation docs in the section Setting parent screen options based on child navigator's state to access information of screens in nested navigators.
Change this line
<Stack.Screen name="Tab" component={TabNav}></Stack.Screen>
to the following (adding the options prop):
<Stack.Screen
name="Tab"
component={TabNav}
options={({ route }) => {
const routeName = getFocusedRouteNameFromRoute(route) ?? 'Home';
switch (routeName) {
case 'Category': {
return {
headerTitle: 'Category',
};
}
case 'Settings': {
return {
headerTitle: 'Settings',
};
}
case 'Home':
default: {
return {
headerTitle: 'Home',
};
}
}
}}
/>
You also have to import getFocusedRouteNameFromRoute, so also add:
import { getFocusedRouteNameFromRoute } from '#react-navigation/native';
If I understand your problem correctly, you wanted to change the stack title when the tab is changed. In this case, you may use React Context to control it.
(I also put this code in snack https://snack.expo.io/#gie3d/change-header-from-tab)
edit: 1, I separated it into 3 files and assume that it's all in the same directory.
HomeTitleContext.js
export const HomeTitleContext = React.createContext({
title: 'default title',
setTitle: () => {},
});
App.js
import { HomeTitleContext } from './HomeTitleContext';
export default function App() {
const [title, setTitle] = useState('default title');
return (
<HomeTitleContext.Provider
value={{
title,
setTitle,
}}
>
<HomeTitleContext.Consumer>
{(ctx) => (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Tab"
component={TabNav}
options={{ title: ctx.title }} // The title will be dynamically changed here
/>
<Stack.Screen
name="Category"
component={OtherScreen}
></Stack.Screen>
<Stack.Screen
name="SpecialBeerScreen"
component={OtherScreen}
></Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
)}
</HomeTitleContext.Consumer>
</HomeTitleContext.Provider>
);
}
In your component, for example: HomeScreen, you set up a useFocusEffect and change the title from setTitle which you'll get from the context
HomeScreen.js
import React, { useContext } from 'react';
import { useFocusEffect } from '#react-navigation/native';
import { View, Text } from 'react-native';
import { HomeTitleContext } from './HomeTitleContext';
const HomeScreen = ({ navigation }) => {
const { setTitle } = useContext(HomeTitleContext);
useFocusEffect(() => {
setTitle('this is home');
});
return (
<View>
<Text>Home</Text>
</View>
);
};

Resources