I'm using react navigation in a react native project and i am unable to find how to go back to navigation root (first screen in app) from an arbitrary screen. Navigation is like this:
<NavigationContainer>
<Stack.Navigator>
{!loggedIn ? (
// Login
<Stack.Group>
<Stack.Screen name="LogInScreen" component={LogInScreen} />
</Stack.Group>
) : (
// App Screens
<Stack.Group>
// This Tab navigator may have more stacks inside
<Stack.Screen name="AppTabsNavigator" component={AppTabsNavigator} />
<Stack.Screen name="SomeErrorOutsideTabs" component={SomeErrorOutsideTabs} />
</Stack.Group>
)}
// Error Modals
<Stack.Group>
<Stack.Screen name="SomeErrorScreen" component={SomeErrorScreen} />
</Stack.Group>
<Stack.Navigator>
</NavigationContrainer>
Use cases are:
User in LogInScreen causes some error -> SomeErrorScreen shows -> User press button to go back to LogInScreen.
User in anywhere else causes some error -> SomeErrorScreen shows -> User press button to go back to first screen in AppTabsNavigator (sometimes does not work depending on inner stacks).
The summary is, no matter where you are, when you press button in SomeErrorScreen you must go back to "first app screen", which is LogInScreen if not logged in or first tab of AppTabsNavigator if logged in.
I have tried using popToTop() but it does not work when there are inners Stacks like SomeResourceStack inside AppTabsNavigator to contain index and show of resource, for example:
User in AppTabsNavigator > SomeResourceStack > index access to AppTabsNavigator > SomeResourceStack > show, cause SomeErrorScreen to appear and when going back, he goes back to AppTabsNavigator > SomeResourceStack > show instead of AppTabsNavigator first tab.
Answer:
The right answer is to use reset like Anthony Marino said and combine it with Ararat Ispiroglu suggestion. I controlled Android's back button behavior as well:
...
import { BackHandler } from 'react-native'
import { useFocusEffect } from '#react-navigation/native'
import { useSelector } from 'react-redux'
...
const SomeErrorScreen = ({ navigation }) => {
...
const loggedIn = useSelector(state => state.auth.loggedIn)
const onGoingBackButtonPress = React.useCallback(() => {
navigation.reset({
index: 0,
routes: [{ name: !loggedIn ? 'LogInScreen' : 'AppTabsNavigator' }]
})
}, [navigation, loggedIn])
const androidBackButtonCallback = React.useCallback(() => {
const onBackPress = () => {
onGoingBackButtonPress()
return true
}
BackHandler.addEventListener('hardwareBackPress', onBackPress)
return () => BackHandler.removeEventListener('hardwareBackPress', onBackPress)
}, [onGoingBackButtonPress])
useFocusEffect(androidBackButtonCallback)
...
return <View>
<Button onPress={onGoingBackButtonPress } />
</View>
}
export default SomeErrorScreen
You can reset your navigator
navigation.reset({
index: 0,
routes: [
{
name: 'SomeStackScreen',
params: { someParam: 'Param1' },
},
],
})
As seen in the docs here
You have two options to do that with your current navigation setup;
a) When you need to go back to root you can check if it's logged in or not
simply navigation.navigate(loggedIn ? "AppTabsNavigator" : "LogInScreen")
b) You can set the same name for the top level screen in the navigation for both logged and non-logged in screens. And navigation will pickup whichever screen is mouthed.
{!loggedIn ? (
// Login
<Stack.Group>
<Stack.Screen name="RootScreen" component={LogInScreen} />
</Stack.Group>
) : (
// App Screens
<Stack.Group>
// This Tab navigator may have more stacks inside
<Stack.Screen name="RootScreen" component={AppTabsNavigator} />
<Stack.Screen name="SomeErrorOutsideTabs" component={SomeErrorOutsideTabs} />
</Stack.Group>
)}
I'm finding difficulty in digesting the official documentation and I'm stuck finding out a solution to move between different stack navigators. I have provided my current implementation and code snippet to explain my problem better.
I have a bottom tab navigator and 2 stack navigators to handle different use cases.
BottomNavigation.js
StackNavigation.js
AuthStackNavigation.js
I have created multiple stack navigators within StackNavigation.js and rendering each StackNavigators within BottomNavigation.js
***StackNavigation.js***
const Stack = createStackNavigator();
const HomeStackNavigator = () => {
return (
<Stack.Navigator initialRouteName="Home" screenOptions={ScreenLogo}>
<Stack.Screen name="HomeScreen" component={Home} />
</Stack.Navigator>
);
}
const ProfileStackNavigator = () => {
return (
<Stack.Navigator initialRouteName="Home" screenOptions={ScreenLogo}>
<Stack.Screen name="MahaExpo" component={Profile} />
</Stack.Navigator>
);
}
export { HomeStackNavigator, ProfileStackNavigator };
And as I said I'm rendering each navigator inside tab navigator to switch between screens.
***BottomNavigation.js***
import { HomeStackNavigator, ProfileStackNavigator } from '../Navigations/StackNavigation'
const Tab = createMaterialBottomTabNavigator();
function BottomNavigation() {
return (
<Tab.Navigator
initialRouteName="Home" >
<Tab.Screen
name="Home"
component={HomeStackNavigator}
/>
<Tab.Screen
name="ProfileStackNavigator"
component={ProfileStackNavigator}
/>
</Tab.Navigator>
)
}
export default BottomNavigation
and I'm rendering this within app.js and inside NavigationContainer. I have created AuthStackNavigation.js which has a login and register screens.
***AuthStackNavigation.js***
const AuthStack = createStackNavigator();
const AuthStackNavigation = ({ }) => (
<AuthStack.Navigator headerMode='none'>
<AuthStack.Screen name="UserLogin" component={UserLogin}></AuthStack.Screen>
<AuthStack.Screen name="UserRegister" component={UserRegister}></AuthStack.Screen>
</AuthStack.Navigator>
);
export default AuthStackNavigation
So currently, I'm showing the home screen with some public content and user can switch to different screens using the bottom tab navigator linked to different screens. When the user clicks on profile tab, im displaying a button to login which should take the user to AuthStackNavigation.js which has a login and register screen. Thanks in advance!
const ProfileStackNavigator = () => {
return (
<Stack.Navigator initialRouteName="Home" screenOptions={ScreenLogo}>
<Stack.Screen name="MahaExpo" component={Profile} />
<Stack.Screen name="UserLogin" component={UserLogin} />
<Stack.Screen name="UserRegister" component={UserRegister} />
</Stack.Navigator>
);
}
on profile page check user is logged in or not, if not, simply navigate to UserLogin screen. Once login simply pop back to profile page are refresh.
There's no way of communicating between two separate navigators if they're not related to each other (one is a child navigator of another or etc.), unless you create something like RootStackNavigator, which would be on the higher level and which would contain all the navigators you have listed above.
Create guest navigators array, authorized navigators array and some shared routes (if needed). Guest navigator contains authstack only and authorized one contains other navigators, if you use redux, context or something like this you can check for the token on startup to determine which navigator you have to use. Logging in will give you the token and will automatically change your navigator to authorized navigators and logging out will throw you back (as you no longer have the token) to the guest navigators which will contain only authentication flow.
Pretty hard to explain ;) hope this helps..
Redux Example
`
const authNavigators = [
{
type: "screen",
key: "authNavigators",
name: "authNavigators",
component: authNavigators,
},
];
const otherNavigators = [
{
type: "screen",
key: "otherNavigators1",
name: "otherNavigator1",
component: otherNavigators1,
},
{
type: "screen",
key: "otherNavigators2",
name: "otherNavigator2",
component: otherNavigators3,
},
...
];
const RootStack = () => {
const { condition } = useSelector(
(state) => state.store);
let navigators = otherNavigators;
if(condition) {
navigators = authNavigators;
}
return (
<RootStackNavigator.Navigator>
{navigators.map((item) => {
return (
<RootStackNavigator.Screen
{...item}
options={({ route: { params } }) => ({
...item.options,
})}
/>
);
})}
</RootStackNavigator.Navigator>
);
};
export default RootStack;`
from redux, you can dispatch an action which would change that condition and this would dynamically update your navigator.
I have pretty simple use case for this, Basically if the screen is focused I want this function to run, if not don't run.
const isFocused = useIsFocused();
const onViewRef = useRef(({ viewableItems }) => {
if (!isFocused) return;
//code to run
});
Here's the stack for this screen in particular:
const MyStack = () => {
return (
<Stack.Navigator headerMode="none">
<Stack.Screen
component={MyStackComponent}
name="My"
/>
</Stack.Navigator>
);
};
And here is that stack being use in a tabNavigator
<Tab.Screen
component={MyStack}
name="My"
/>
<Tab.Screen
component={Other Stack}
name="Other"
/>
So basically whenever I go to any other tab on my app, the file using the useIsFocused hook should should return false right? Because right now it returns true no matter what, and I can't have that function running all the time. Any pointers?
to bring a real answer to this topic.
For some reason, in my case too, useIsFocused() always return true; from the moment I arrive on the screen in question for the first time untill I kill the app (IOS).
So I found another solution: use the navigation.isFocused() method from react-navigation. (https://reactnavigation.org/docs/navigation-prop/#isfocused)
And it works perfectly !
Hoping it will help someone
Here is my example:
useEffect(() => {
notificationListener.current = Notifications.addNotificationReceivedListener(notification => {
if (navigation.isFocused()) {
fetchConversationsList();
}
});
}, [fetchConversationsList, navigation]);
https://reactnavigation.org/docs/tab-based-navigation
tabBarIcon: ({ focused, color, size })
The Goal
Using react navigation, navigate from a screen in a navigator to a screen in a different navigator.
More Detail
If I have the following Navigator structure:
Parent Navigator
Nested Navigator 1
screen A
screen B
Nested Navigator 2
screen C
screen D
how do I go from screen D under nested navigator 2, to screen A under nested navigator 1? Right now if I try to navigation.navigate to screen A from screen D there will be an error that says it doesn't know about a screen A, only screen C and D.
I know this has been asked in various forms in various places on this site as well as GitHub(https://github.com/react-navigation/react-navigation/issues/983, https://github.com/react-navigation/react-navigation/issues/335#issuecomment-280686611) but for something so basic, there is a lack of clear answers and scrolling through hundreds of GitHub comments searching for a solution isn't great.
Maybe this question can codify how to do this for everyone who's hitting this very common problem.
In React Navigation 5, this becomes much easier by passing in the screen as the second parameter:
navigation.navigate('Nested Navigator 2', { screen: 'screen D' });
You can also include additional levels if you have deeply nested screens:
navigation.navigate('Nested Navigator 2', {
screen: 'Nested Navigator 3', params: {
screen: 'screen E'
}
});
Update: For React Navigation v5, see #mahi-man's answer.
You can use the third parameter of navigate to specify sub actions.
For example, if you want to go from screen D under nested navigator 2, to screen A under nested navigator 1:
this.props.navigation.navigate(
'NestedNavigator1',
{},
NavigationActions.navigate({
routeName: 'screenB'
})
)
Check also:
https://reactnavigation.org/docs/nesting-navigators/
React Navigation v3:
Navigation.navigate now takes one object as the parameter. You set the stack name then navigate to the route within that stack as follows...
navigation.navigate(NavigationActions.navigate({
routeName: 'YOUR_STACK',
action: NavigationActions.navigate({ routeName: 'YOUR_STACK-subRoute' })
}))
Where 'YOUR_STACK' is whatever your stack is called when you create it...
YOUR_STACK: createStackNavigator({ subRoute: ... })
On React Navigation v5 you have here all the explanation:
https://reactnavigation.org/docs/nesting-navigators#navigating-to-a-screen-in-a-nested-navigator
Route definition
function Root() {
return (
<Stack.Navigator>
<Stack.Screen name="Profile" component={Profile} />
<Stack.Screen name="Settings" component={Settings} />
</Stack.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Drawer.Navigator>
<Drawer.Screen name="Home" component={Home} />
<Drawer.Screen name="Root" component={Root} />
</Drawer.Navigator>
</NavigationContainer>
);
}
Instruction
navigation.navigate('Root', { screen: 'Settings' });
In React Navigation v5, you can do something like:
navigation.navigate('Root', {
screen: 'Settings',
params: {
screen: 'Sound',
params: {
screen: 'Media',
},
},
});
In the above case, you're navigating to the Media screen, which is in a navigator nested inside the Sound screen, which is in a navigator nested inside the Settings screen.
In React Navigation v5/v6
Navigation to Specific Screen on a Stack
navigation.navigate('Home', {
screen: 'Profile',
params: {userID: 1}
}
)
What If We Nest More?
Consider this structure:
NAVIGATOR:
*StackA
*ScreenC
*ScreenD
*StackB
*ScreenI
*StackE
*ScreenF
*StackG
*ScreenJ
*ScreenH
We want to get from ScreenC inside StackA all the way to ScreenH in StackB.
We can actually chain the parameters together to access specific screens.
navigation.navigate('StackB',{
screen: 'StackE',
params: {
screen: 'StackG',
params: {
screen: 'ScreenH'
}
}
}
)
For more information
In React Navigation 3
#ZenVentzi, Here is the answer for multi-level nested navigators when Nested Navigator 1 has Nested Navigator 1.1.
Parent Navigator
Nested Navigator 1
Nested Navigator 1.1
screen A
screen B
Nested Navigator 2
screen C
screen D
We can just inject NavigationActions.navigate() several times as needed.
const subNavigateAction = NavigationActions.navigate({
routeName: 'NestedNavigator1.1',
action: NavigationActions.navigate({
routeName: 'ScreenB',
params: {},
}),
});
const navigateAction = NavigationActions.navigate({
routeName: 'NestedNavigator1',
action: subNavigateAction,
});
this.props.navigation.dispatch(navigateAction);
UPDATE
For React Navigation 5, please check #mahi-man's answer above.
https://stackoverflow.com/a/60556168/10898950
React Navigation v6
docs
function Home() {
return (
<Tab.Navigator>
<Tab.Screen name="Feed" component={Feed} />
<Tab.Screen name="Messages" component={Messages} />
</Tab.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={Home}
options={{ headerShown: false }}
/>
<Stack.Screen name="Profile" component={Profile} />
<Stack.Screen name="Settings" component={Settings} />
</Stack.Navigator>
</NavigationContainer>
);
}
navigation.navigate('Home', { screen: 'Messages' });
try this,
Parent Navigator
Nested Navigator 1
screen A
screen B
Nested Navigator 2
screen A
screen C
screen D
and then, there is no need to go A in 1 from D in 2, you can just go A from D both in 2, you can check here image or A stack navigator for each tab
Complete freedom: singleton w/ navigationOptions
If you have a situation where you have multiple navigation stacks and sub stacks, this can be frustrating to know how to get a reference to the desired stack given how React Navigation is setup. If you were simply able to reference any particular stack at any given time, this would be much easier. Here's how.
Create a singleton that is specific to the stack you want to reference anywhere.
// drawerNavigator.js . (or stackWhatever.js)
const nav = {}
export default {
setRef: ref => nav.ref = ref,
getRef: () => nav.ref
}
Set the reference on desired navigator using navigatorOptions
import { createBottomTabNavigator } from 'react-navigation'
import drawerNavigation from '../drawerNavigator'
const TabNavigation = createBottomTabNavigator(
{
// screens listed here
},
{
navigationOptions: ({ navigation }) => {
// !!! secret sauce set a reference to parent
drawerNavigation.setRef(navigation)
return {
// put navigation options
}
}
}
)
Now you can reference drawerNavigator anywhere inside or outside
// screen.js
import drawerNavigator from '../drawerNavigator'
export default class Screen extends React.Component {
render() {
return (
<View>
<TouchableHighlight onPress={() => drawerNavigator.getRef().openDrawer()}>
<Text>Open Drawer</Text>
</TouchableHighlight>
</View>
)
}
}
Explanation
Within Step 2, a Tab Navigator is one of the screens within a Drawer Navigator. Tab Navigator needs to close the drawer but also anywhere within your app, you can call drawerNavigator.getRef().closeDrawer() after this step is performed. You are not limited to having direct access to props.navigation after that step.
If nothing else works (as in my case), just do:
Main/Root/App.js:
<StackNavigator ref={(x) => (global.stackNavigator = x)} />
Anywhere:
global.stackNavigator.dispatch(
NavigationActions.navigate({
routeName: 'Player',
params: { },
}),
);
In React Navigation V5, you can do like this:
but remember to you placing this on the parent side
this.props.navigation.navigate(
'Nested Navigator 1',
{name: 'jane'},
this.props.navigation.navigate('Screen A', {id: 2219}),
);
While working on a react-native project, i came across same situation. I have tried multiple ways in navigating to screen but failed.
After many trials, I tried passing parents navigation object to children and made a navigation function call and it worked.
Now coming to your issues, If you want to navigation from screen D to screen A do follow these steps.
-> Pass nested navigator 2 navigation props to its children using screenProps.
export default class Home extends Component {
static navigationOptions = {
header:null
};
constructor(props) {
super(props);
this.state = {
profileData: this.props.navigation.state.params,
route_index: '',
}
}
render() {
return (
<ParentNavigator screenProps={this.props.navigation} />
);
}
}
export const ParentNavigator = StackNavigator({
// ScreenName : { screen : importedClassname }
Nested1: { screen: nested1 },
Nested2: { screen : nestes1 }
});
export const nested1 = StackNavigator({
ScreenA: { screen: screenA },
ScreenB: { screen : screenB }
});
export const nested2 = StackNavigator({
ScreenC: { screen: screenC },
ScreenD: { screen : screenD }
});
You can receive the navigation in children using
const {navigate} = this.props.screenProps.navigation;
Now this navigate() can be used to navigate between children.
I accept that this process is little confusing but i couldn't find any solutions so had to go with this as i have to complete my requirement.
I've found also such solution here:
onPress={() =>
Promise.all([
navigation.dispatch(
NavigationActions.reset({
index: 0,
// TabNav is a TabNavigator nested in a StackNavigator
actions: [NavigationActions.navigate({ routeName: 'TabNav' })]
})
)
]).then(() => navigation.navigate('specificScreen'))
}
const subAction = NavigationActions.navigate({ routeName: 'SignInScreen' });
AsyncStorage.clear().then(() =>
this.props.navigation.navigate('LoggedOut', {}, subAction));
LoggedOut is the stack name where signIn screen is placed.
My goal was to have the authentication screens all share the same background and the rest of the app using the regular stack transition.
After hours I've found the solution is to have the createStackNavigator() in the same file as your component wrapper. So that you can successfully expose the static router as the document stated. This will avoid the You should only render one navigator explicitly in your app warning and you can use this.props.navigation.navigate('AnyScreen') to navigate to any nested screen.
AuthRouter.js
export const AuthNavigationStack = createStackNavigator(
{
Login: {
screen: Login
},
CreateAccount: {
screen: CreateAccount
}
}
);
export default class AuthContainer extends React.Component {
constructor( props ) {
super( props );
}
static router = AuthNavigationStack.router;
render() {
return (
<ImageBackground
style={ {
width: '100%',
height: '100%'
} }
source={ require( '../Images/johannes-andersson-yosimite.jpg' ) }
blurRadius={ 10 }
>
<StatusBar
barStyle="dark-content"
backgroundColor="transparent"
translucent={ true }
/>
<AuthNavigationStack navigation={ this.props.navigation } />
</ImageBackground>
);
}
}
MessengerRouter.js
export const MessengerStackNavigator = createStackNavigator(
{
Chat: {
screen: Chat,
},
User: {
screen: User,
},
}
);
export default class MainContainer extends React.Component {
constructor( props ) {
super( props );
}
static router = MessengerStackNavigator.router;
render() {
return <MessengerStackNavigator navigation={ this.props.navigation } />;
}
}
Router.js
import { createStackNavigator } from 'react-navigation';
import AuthRouter from './AuthRouter';
import MessengerRouter from './MessengerRouter';
export const RootNavigationStack = createStackNavigator( {
AuthContainer: {
screen: AuthRouter,
navigationOptions: () => ( {
header: null
} )
},
MessengerRouter: {
screen: MessengerRouter,
navigationOptions: () => ( {
header: null
} )
}
} );
RootContainer.js
import { RootNavigationStack } from '../Config/Router';
class RootContainer extends Component {
render() {
return <RootNavigationStack />;
}
}
Notes:
Pass header: null from the RootNaviagtionStack to the nested stacks to remove the overlapping header
If you navigate from Nested A to Nested B and use the back button, it will return you to the first screen in Nested B. Not a big problem but I haven't figured out how to fix it.
This is another way to navigate to nested screen using Version: 5.x. It worked without any additional configuration. More info here: https://reactnavigation.org/docs/use-link-to
const linkTo = useLinkTo();
// ...
// Just call this
linkTo(`/main/sub/subB`);
I have a TabNavigator as shown in the picture.
Header Image
I am using TabNavigator for creating these tabs as below.
const Tab_Navigator = TabNavigator({
First:{
screen: First,
},
Second:{
screen: Second,
},
Third:{
screen: Third,
},
Now I want to block/disable "Second" and "Third" tabs. It should be visible but one shouldn't able to navigate to them.
I tried blocking these tabs as shown here but I guess I am missing something. My try:
Tab_Navigator.router.getStateForAction = (action, state) => {
if( action.type === NavigationActions.navigate({ routeName: "Second"}) ||
action.type === NavigationActions.navigate({ routeName: "Third"}))
{
return null;
}
return Byte.router.getStateForAction(action, state);
};
In this case, the action.type = "Navigation/NAVIGATE" and action.routeName is the name of your tab. It is just a little different from the ReactNavigation Routers example. The following should work:
const defaultGetStateForAction = Tab_Navigator.router.getStateForAction;
Tab_Navigator.router.getStateForAction = (action, state) => {
if ((action.type === NavigationActions.NAVIGATE) &&
(action.routeName === "Second" || action.routeName === "Third") {
return null;
}
return defaultGetStateForAction(action, state);
};
EDIT: Here is an image of the the Chrome Debugger stopped at a breakpoint in a very similar piece of code(tab names are different), but it shows the values of the "action" object being passed into this function.
For Version 5.x+ there's a new way to do it.
<Tabs.Screen
name="Chat"
component={Chat}
listeners={{
tabPress: e => {
// Prevent default action
e.preventDefault();
},
}}
/>
Here's the reference link to the docs: https://reactnavigation.org/docs/navigation-events/
You have to use tabBarOnPress propert under defaultNavigationOptions, and check the route name to which you dont want to navigate return them null else return defaultHandler. Please check the following code
const Tab_Navigator = createBottomTabNavigator({
First:{
screen: First,
},
Second:{
screen: Second,
},
Third:{
screen: Third,
}
}, defaultNavigationOptions: ({ navigation }) => ({
tabBarOnPress: ({ navigation, defaultHandler }) => {
if (
navigation.state.routeName === "Second" ||
navigation.state.routeName === "Third"
) {
return null;
}
defaultHandler();
},})
This is my solution to disable and completely take control over the tabBarButton in React Navigation 6.
// Defining the disabled tabBarButton component
//
const DisabledTabBarButton = ({ style, ...props }: BottomTabBarButtonProps) => (
<Pressable disabled style={[{ opacity: 0.2 }, style]} {...props} />
)
const Tab = createBottomTabNavigator()
const Router = () => (
<Tab.Navigator>
<Tab.Screen name="Screen 1" />
<Tab.Screen name="Screen 2"
options={{
// Applying the disabled button
tabBarButton: DisabledTabBarButton,
}}
/>
</Tab.Navigator>
export default Router