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 am trying to implement a react navigation authentication flow where there are total three screens and two screen of them are in Authentication ( Login Screen and Registration Screen ) which are used to authenticate a user and the other one is Home Screen ( Feed Screen ) which shows the application's home.
Authenication Screens are in a stack navigator which is AuhenticationNavigation and the home screen is in the HomeScreenNav stack navigator. I have these two stack navigator which are used to navigate.
As in the Code Snippet.
The logic I want to implement is that after successful login the screen should navigate to Feed Screen and in Feed Screen I have a logout function which is used to logout and change the persistent state which is isLoggedIn to know that a user is logged in or not.
const RootStack = createStackNavigator();
class AuthenticationNavigation extends Component {
render() {
return(
<RootStack.Navigator headerMode='none'>
<RootStack.Screen name='Login' component={Login}/>
<RootStack.Screen name='Register' component={Register} />
</RootStack.Navigator>
);
}
}
class HomeScreenNav extends Component {
render() {
return(
<RootStack.Navigator headerMode='none'>
<RootStack.Screen name='Feed' component={HomeScreen} />
</RootStack.Navigator>
);
}
}
Two ways I tried to resolve it:
1 :-
<NavigationContainer>
{ this.state.isLoggedIn ? (
<HomeScreenNav />
) : (
<AuthenticationNavigation />
) }
</NavigationContainer>
Above Code snippet shows me Feed Screen is the isLoggedIn is true and evaluates to Authentication Screen is the it is false.
But what if I want to navigate to the Auth Screens after a successfull logout in Feed Screen.
I have also the tried the react navigation authentication flow from navigation docs but I am not able to resolve my error.
2:-
<NavigationContainer>
<RootStack.Navigator
initialRouteName={ (this.state.isLoggedIn == true) ? 'Home' : 'Auth' }
headerMode='none'
>
<RootStack.Screen
name='Home'
component={HomeScreenNav}
/>
<RootStack.Screen
name='Auth'
component={AuthenticationNavigation}
/>
</RootStack.Navigator>
</NavigationContainer>
In this way I am unable to open the Home Screen when isLoggedIn evaluates to true and it shows me Auth screen. I think that the problem is in isLoggedIn state but again I checked whether the state is really working or not properly and it resloved into a successful evaluation. But still it is evaluating as false.
Here is the process of retrieving the state which is isLoggedIn.
constructor(props) {
super(props);
this.state = {
tokenId: '',
isLoggedIn: false,
}
}
componentDidMount = async () => {
const token = await AsyncStorage.getItem('token');
this.setState({tokenId: token});
this.setState({isLoggedIn: !Object.is(this.state.tokenId, null)});
await AsyncStorage.setItem('isLoggedIn', this.state.isLoggedIn ? 'Y' : 'N');
console.log(this.state.tokenId, this.state.isLoggedIn);
}
Request: Please provide a proper solution not any documentation links because I have searched for this problem many times on many platforms.
The structure of my React Navigation is
DashboardScreen => 3 Bottom Tabs (1 out of 3, again have) => 2 Bottom Tabs
The DashboardScreen have three BottomTabs
Main
Profile
Products, the Products have again two BottomTabs
a. AllProducts
b. SoldProducts
What I want to do is, when a product is sold, the product owner will receive a notification, and onNotificationOpened from DashboardScreen.js I want to navigate to SoldProducts. OneSignal onNotificationOpened is registered in DashboardScreen.js. (As far as I know, that's the screen where I should implement my OneSignal's working, I have three more cases here.)
DashboardScreen.js
useEffect(() => {
if (isLoading) {
loadUserDetail();
}
OneSignal.init(ONE_SIGNAL_APP_ID, {
kOSSettingsKeyAutoPrompt: false,
kOSSettingsKeyInAppLaunchURL: false,
kOSSettingsKeyInFocusDisplayOption: 2,
});
OneSignal.inFocusDisplaying(2);
OneSignal.addEventListener('received', onReceived);
OneSignal.addEventListener('opened', onOpened);
OneSignal.addEventListener('ids', onIds);
}, [isLoading, loadUserDetail, navigation, onOpened]);
const onOpened = useCallback((openResult) => {
const additionalData = openResult.notification.payload.additionalData;
if (additionalData.type === 'sold') {
// From here I want to navigate
console.log('DashboardScreen, Move to Product Tab, and open SOLD PRODUCT TAB, instead of All Products Tab');
}
}, []);
return (
<Tab.Navigator
initialRouteName="Main"
lazy={true}
tabBar={(props) => (
<CustomTabBar />
)}>
<Tab.Screen name="Profile" component={Profile} />
<Tab.Screen name="Main" component={Main} />
<Tab.Screen name="Products" component={Products} />
</Tab.Navigator>
);
Solutions I have tried so far is
navigation.dispatch(
NavigationActions.navigate({
routeName: 'DashboardScreen',
action: NavigationActions.navigate({
routeName: 'Products',
}),
}),
);
But that's even not opening the Products on DashboardScreen.
Also found
TabActions.jumpTo('Tab')
but TabActions only working in TabComponenet, means inside Main, or Profile to switch between Tabs
Any help will be highly appreciated.
how to give dynamic initial route name in the react-navigation? if the unit exists we have to redirect to another route or else we have to take user another route.
Note: I'm creating a bottom tab navigator in which I have to set an initial route to that particular bottom tab navigator.
(Not the authentication flow)
import React, {Component} from 'react';
import {createAppContainer} from 'react-navigation';
import {createMaterialBottomTabNavigator} from 'react-navigation-material-bottom-tabs';
... imports
function getInitialScreen() {
AsyncStorage.getItem('unit')
.then(unit => {
return unit ? 'Home' : 'secondTab';
})
.catch(err => {
});
}
const TabNavigator = createMaterialBottomTabNavigator(
{
Home: {
screen: HomeScreen,
navigationOptions: {
.....navigation options
},
},
secondTab: {
screen: secondTab,
},
},
{
initialRouteName: getInitialScreen(),
},
);
export default createAppContainer(TabNavigator);
See according to the docs, initialRoute name should not be a async func .
So ideally what you should do is , anyways you need a splashscreen for your app right, where you display the logo and name of app. Make that page the initialRoute and in its componentDidMount, check for the async function and navigate to ddesired page.
Like what ive done :
createSwitchNavigator(
{
App: TabNavigator,
Auth: AuthStack,
SplashScreen: SplashScreen,
},
{
initialRouteName: 'SplashScreen',
},
),
And inside SplashScreen im doing :
componentDidMount(){
if (token) {
this.props.navigation.navigate('App');
} else {
this.props.navigation.navigate('Auth');
}
}
Hope its clear. Feel free for doubts
As you can see here:
If you need to set the initialRouteName as a prop it is because there
is some data that you need to fetch asynchronously before you render
the app navigation. another way to handle this is to use a
switchnavigator and have a screen that you show when you are fetching
the async data, then navigate to the appropriate initial route with
params when necessary. see docs for a full example of this.
Take a look at here.
You'll find more description!
Also quick fix for this situation is check your condition inside SplashScreen componentDidMount() function
Example of SplashScreen :
componentDidMount(){
AsyncStorage.getItem('unit')
.then(unit => {
if(unit){
this.props.navigation.navigate('Home')
}else{
this.props.navigation.navigate('secondTab')
}
})
.catch(err => {
});
}
You can check the condition on the main page or App.js
render() {
const status = get AsyncStorage.getItem('unit');
if(status != null)
{ return <Home/> }
else
{ return <AnotherScreen/> }
}
But we can switch between pages if we use stacknavigator or switchnavigator..
We cant goto the particular tabs directly according to my knowledge. (Correct me if I am wrong).
The official documentation gives you this example to achieve pretty much what you want:
isSignedIn ? (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</>
) : (
<>
<Stack.Screen name="SignIn" component={SignInScreen} />
<Stack.Screen name="SignUp" component={SignUpScreen} />
</>
)
Ok So in your navigation page create state and change its value accordingly AsyncStorage like
render() {
const status = get AsyncStorage.getItem('unit');
if(status != null)
{ setState({ name : 'Home'}) }
else
{ setState({ name : 'anotherTab'}) }
}
then pass that state to tabNavigation
initialRouteName: this.state.name,
and this state , setState functions are classed based so you have to use useState instead to initial state on your page
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`);