How to navigate from Home screen to Login screen in a nested navigation ? React Navigation v6 - react-native

So I'm trying to improve my navigation in my React Native project using React Navigation. I would like to know how to navigate through a nested navigator from Home to Login screen.
navigation.ts
export type RootStackParamList = {
AuthorizedTabStack: BottomTabScreenProps<AuthorizedTabNavigationList>;
AuthorizedModalStack: NavigatorScreenParams<AuthorizedModalList>;
UnauthorizedStack: NavigatorScreenParams<UnauthorizedStackList>;
};
export type AuthorizedTabNavigationList = {
Home: undefined;
Planner: undefined;
};
export type AuthorizedModalList = {
InputModal: undefined;
};
export type UnauthorizedStackList = {
Login: undefined;
};
In my MainNavigator.tsx, I've implemented this...
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false }}>
{auth.currentUser ? (
<>
<Stack.Screen name="AuthorizedTabStack" component={TabNavigation} />
<Stack.Screen
name="AuthorizedModalStack"
component={ModalNavigation}
/>
</>
) : (
<Stack.Screen
name="UnauthorizedStack"
component={UnauthorizedStack}
/>
)}
</Stack.Navigator>
</NavigationContainer>
The UnauthorizedStackList is basically a StackNavigator
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Login" component={LoginScreen} />
</Stack.Navigator>
I tried using this and there is an error: The action 'NAVIGATE' with payload {"name":"UnauthorizedStack","params":{"screen":"Login"}} was not handled by any navigator.
Do you have a screen named 'UnauthorizedStack'?
const navigation = useNavigation<NavigationProps>();
const handlePressLogOut = () => {
logOut()
navigation.navigate('UnauthorizedStack', { screen: 'Login' });
};
Please let me know if there are better practices on nested navigator or anything else in the code as well. I would like to learn more!

Your navigation.navigate(...) is called before auth state updates, because changing state is not happening right away. You cannot navigate to a screen, that is not rendered.
A few notes about your code:
you don't have to call navigation.navigate(...) after logging out, because you are conditionally rendering screens with auth.currentUser, so UnauthorizedStack will be rendered right after logOut(),
if Login screen is the only screen in your stack, probably there is no need for using stack,

Related

Expo React-Native Web Browser Back Button

I'm looking at using React-Native and Expo to build a web app. The only real issue I'm running into is allowing the use of the browser back button. I'm currently using #react-navigation/native to navigate between screens.
Unfortunately, when changing screens, the browser doesn't seem to register this as a change of page and so the browser back button never works. I could certainly use the standard back button used within the app to navigate back but I'd like people to be able to use the browser back button as that's more intuitive. I'm using the following in my App.js:
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
const Stack = createNativeStackNavigator();
const App = () => {
...
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ headerShown: false }}
/>
<Stack.Screen
name="Settings"
component={SettingsScreen}
options={{ headerShown: false }}
/>
</Stack.Navigator>
</NavigationContainer>
)
};
export default () => {
return (
<App />
);
};
So, is it possible using Expo and React-Native to allow the use of the browser back button for apps built for the web?

react native can not push a screen from 'screen modal'

i have a modal screen like below:
<Stack.Screen name="ForwardChatContent" component={ForwardChatContentScreen}
options={{
presentation: 'modal',
}} />
I want to push a screen from this screen, for example i have other screen like below:
<Stack.Screen name="ForwardChatToUser" component={ForwardChatToUserScreen}
But when using navigate, it's does not show new screen, can someone help? Thanks
Update, i changed ForwardChatContent and ForwardChatToUser into stack navigator like this:
const forwardStack = () => {
return <Stack.Navigator>
<>
<Stack.Screen name="ForwardChatContent" component={ForwardChatContentScreen}
options={{
presentation: 'modal',
}} />
<Stack.Screen name="ForwardChatToUser" component={ForwardChatToUserScreen}
options={{
// presentation: 'modal',
}} />
</>
</Stack.Navigator>
}
when navigate im using this code:
RootNavigation.navigate('ForwardChat', {message : props.currentMessage})
But in ForwardChatContent i got error ERROR TypeError: undefined is not an object (evaluating 'route.params.message')*
Because Im using this code to get message :
const message = route.params.message
Can u provide some way to get the params, thanks
It's because when you open a screen as Modal, it is treated as a separate set out of your existing Navigation Stack, it expects a Modal to be a NavigationStack, not just a Screen.
In your case, ForwardChatContentScreen is just a simple <Stack.Screen> it doesn't have a navigation stack.
Change it to NavigationStack from Screen it will work and open the NavigationStack as Modal having your screen as root, then it will work.
Check demo here
Cheers.

How to specify a default screen for react native stack navigator?

I want to navigate between two screens in my RN application: Daily and AllTodos. I want to make sure that when the navigator renders Daily by default. This is the code which I have used which accords the documentation :
const StackNavigator = ({component}) => {
return (
<NavigationContainer independent={true}>
<Stack.Navigator initialRouteName="Daily">
<Stack.Screen
name="Daily"
component={Daily}
options={{headerShown: false}}
/>
<Stack.Screen
name="AllTodos"
component={AllTodos}
options={{headerShown: false}}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
But still AllTodos page is rendered by default. Where am i going wrong?
You don't need to destructure the 'component' props. Take it off.
const StackNavigator = ({component}) // take off the component props => {

Dismiss the nested stack navigator

In my react-native project I am using libraries
"#react-navigation/native": "^5.8.10",
"#react-navigation/stack": "^5.12.8",
I have nested navigator, like this:
// root level I have a stack navigator where it contains two screens, `Home` and `Settings`.
const App = ()=> {
const rootStack = createStackNavigator();
return (
<NavigationContainer>
<rootStack.Navigator>
<rootStack.Screen name="Home" component={Home} />
<rootStack.Screen name="Settings" component={Settings} />
</rootStack.Navigator>
</NavigationContainer>
);
}
// The Settings screen is a nested stack navigator
const Settings = ()=> {
const settingsStack = createStackNavigator();
return (
<settingsStack.Navigator>
<settingsStack.Screen name="SettingsOne" component={SettingsOneScreen} />
<settingsStack.Screen name="SettingsTwo" component={SettingsTwoScreen} />
</settingsStack.Navigator>
);
}
As you can see, the Settings screen is actually another level (nested) stack navigator.
On SettingsOneScreen, there is a button navigates user to SettingsTwoScreen.
const SettingsOneScreen = ({navigation}) => {
...
return (
...
<Button onPress={()=>navigation.navigate("SettingsTwo")}/>
)
}
Now, on SettingsTwoScreen, I have a button, I would like to close the whole settings navigator stack when user tap on the button. That's dismiss the whole settings stack & show user the Home. How to achieve it?
const SettingsTwoScreen = ({navigation}) => {
...
return (
...
<Button onPress={/*dismiss the settings stack*/}/>
)
}
(Of course I can't use the navigation.goBack() which only navigate user back to the previous screen i.e. SettingOneScreen in this case.)
1-) use navigate.
//this will go back to Home and remove any screens after that.
navigation.navigate('Home')
docs say that.
In a stack navigator, calling navigate with a screen name will result in different behavior based on if the screen is already present or not. If the screen is already present in the stack's history, it'll go back to that screen and remove any screens after that. If the screen is not present, it'll push a new screen.
2-) use reset.
navigation.reset({
index: 0,
routes: [{ name: 'Home' }],
})}
see docs about reset
3-) use replace then goBack.
//from SettingsOne call replace instead navigate
//this will remove SettingsOne and push SettingsTwo
navigation.replace('SettingsTwo');
//then from SettingsTwo
//calling goBack will back to home because SettingsOne removed in last step
navigation.goBack();
4-) use one stack with pop.
import { StackActions } from '#react-navigation/native';
//merge two stacks in one
<NavigationContainer>
<rootStack.Navigator>
<rootStack.Screen name="Home" component={Home} />
<rootStack.Screen name="SettingsOne" component={SettingsOneScreen} />
<rootStack.Screen name="SettingsTwo" component={SettingsTwoScreen} />
</rootStack.Navigator>
</NavigationContainer>
//back 2 screen
navigation.dispatch(StackActions.pop(2));
see docs about pop
for methods 1, 2 you can try snack here.
I have faced the same problem before, this will help you more
<Button onPress={()=>navigation.navigate("Settings",{
screen: 'SettingsTwo',
params: { data: data }//put here the data that you want to send to SettingTow
)}
/>
//more explanation
goto = (data) => {
navigation.navigate('parent_stack', {
screen: 'screen_on_children_stack',
params: { data: data }
});
}
You can create a Switch navigator for the "root" app and create two stacks "home" and "setting".
const Root = ()=> (createAppContainer(createSwitchNavigator(
{
Home: Home,
Settings: Settings,
},
{
initialRouteName: 'Home',
}
)
// The Settings screen is a nested stack navigator
const Settings = ()=> {
const settingsStack = createStackNavigator();
return (
<settingsStack.Navigator>
<settingsStack.Screen name="SettingsOne" component={SettingsOneScreen} />
<settingsStack.Screen name="SettingsTwo" component={SettingsTwoScreen} />
</settingsStack.Navigator>
);
}
Then you can easily switch between stacks of Home and Settings
this.props.navigation.navigate('Settings');
https://reactnavigation.org/docs/upgrading-from-4.x/#dismiss
navigation.dangerouslyGetParent().pop();

react navigation, get name of nested route

I have a nested drawer navigator below, I am using a custom component in the header:
header: props => {
return <DrawerHeader {...props} />;
},
When I try and access from props the current route in my header, like below, the title is undefined, how can I get the current route?
render() {
const {
navigation,
videos,
search: {term},
scene: {
route: {routeName: title}, // undefined
},
} = this.props;
return (
<View>
<View style={styles.container}>
Navigator:
function DrawerStack() {
return (
<Drawer.Navigator>
<Drawer.Screen
name="VideoEpisodesScreen"
component={VideoEpisodesScreen}
/>
<Drawer.Screen name="TestYourselfScreen" component={TestYourselfScreen} />
<Drawer.Screen name="MyResultsScreen" component={MyResultsScreen} />
<Drawer.Screen name="AboutScreen" component={AboutScreen} />
<Drawer.Screen name="TestsScreen" component={TestsScreen} />
<Drawer.Screen
name="BookmarkedVideosScreen"
component={BookmarkedVideosScreen}
/>
</Drawer.Navigator>
);
}
export default function AppNavigator() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={stackOptions}
/>
<Stack.Screen
name="Drawer"
component={DrawerStack}
options={drawerOptions}
/>
<Stack.Screen
name="MyResultsScreen"
component={MyResultsScreen}
options={options}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
Funnily enough I had the exact same problem and I found your question after it was just an hour old. Essentially the problem is that React Navigation will only give you the current route of the navigator containing the header. If you have a nested navigator, you won't be able to get it.
It looks like this is somewhat intentional, but I've found that by manually querying the state of the navigator, you can drill down to the "deepest" navigator route. Note that while this works for react-navigation 5, it may not work in the future.
You can iteratively query the nested state like this:
const state = navigation.dangerouslyGetState();
let actualRoute = state.routes[state.index];
while (actualRoute.state) {
actualRoute = actualRoute.state.routes[actualRoute.state.index];
}
Note that this is extremely brittle, but it seems to work good enough for my use cases. You should consider creating an issue/feature request on the react-navigation repository for supporting this use case officially.
React Navigation v6 solution:
A nested route object can be discovered through parent Screen listeners. It's given in a callback argument, and can be passed to getFocusedRouteNameFromRoute to get the route name. In the example shown below, I chose to utilize it during the event 'state' (whenever state changes in the nested stack), but you can use it elsewhere if you want.
<Screen
listeners={({ route }) => ({
state: () => {
const subRoute = getFocusedRouteNameFromRoute(route)
// Your logic here //
}
})}
/>
I think in react-navigation 5, you can access route from this.props
const { route } = this.props;