New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to navigate from nested StackNavigator to parent's router screen? #335

Closed
goncalomarques opened this Issue Feb 14, 2017 · 41 comments

Comments

Projects
None yet
@goncalomarques

goncalomarques commented Feb 14, 2017

I have several nested navigators with the following structure:

StackNavigator
    Home
    Login
    Authenticated -> CustomTabNavigator
        ScreenOne -> StackNavigator
            ScreenOneDetails
            ScreenOneSettings
        ScreenTwo

How can I navigate from ScreenOneDetails to Home?

Already tried to dispatch it as an action:

const resetAction = NavigationActions.reset({
    index: 0,
    actions: [
        NavigationActions.navigate({ routeName: 'Home' })
    ]
});
this.props.navigation.dispatch(resetAction);
this.props.navigation.goBack(null);

But all I get is an error - There is no route defined for key splash. Must be one of: 'ScreenOneDetails','ScreenOneSettings'

I know that this makes sense, as the navigation prop that is passed to ScreenOneDetails is the one created by the ScreenOne StackNavigator. However, is there any way to access the navigation prop created by parent navigators? And how would I go the other way around, e.g: from Login to ScreenOneSettings?

@nico1510

This comment has been minimized.

Contributor

nico1510 commented Feb 17, 2017

First off, I'm having the same questions but here's an idea what you could try:

And how would I go the other way around, e.g: from Login to ScreenOneSettings?

The docs include an example I think. Specifically:

  • action - (advanced) The sub-action to run in the child router, if the screen is a navigator.

So you would call something like:

navigate('ScreenOne', {}, NavigationActions.navigate({ 'ScreenOneSettings' }));

As to your first problem:

However, is there any way to access the navigation prop created by parent navigators?

have you tried passing down the navigation prop from the root StackNavigator as a prop down to your child StackNavigator ? Technically I think this is how it should work (by reading this section of the docs), but in practice I got some errors when trying this.

@goncalomarques

This comment has been minimized.

goncalomarques commented Feb 17, 2017

Thanks @nico1510

I had already tried that before creating this issue, but today I managed to make it work. I still have some issues related to the CustomTabNavigator navigationOptions, as I'm passing a TabsNavigator and want to keep the top navbar dynamically linked to the tabs pages

@zoontek

This comment has been minimized.

zoontek commented Feb 17, 2017

@goncalomarques How did you make it work? I've exactly the same problem.

@goncalomarques

This comment has been minimized.

goncalomarques commented Feb 17, 2017

@zoontek I created wrapper components that I use in the screen property for a route I set. In these wrapper components, I pass down the navigation to the nested router in the screenProps prop as whatever I want to call it, in this case rootNavigation:

const NestedNavigator = StackNavigator({
    ScreenOne: {
        screen: ScreenOneComponent
    },
    ScreenTwo: {
        screen: ScreenTwoComponent
    }
});

class NestedNavigatorWrapper extends React.Component {
    constructor(props)  {
        super(props);
    }
    render() {
        return (
            <DashboardNavigator screenProps={{ rootNavigation: this.props.navigation }} />
        );
    }
}
const AppNavigator = StackNavigator({
    Home: {
        screen: HomeComponent
    },
    Nested: {
        screen: NestedNavigatorWrapper
    }
});

To use navigate('Home') inside ScreenOne, I use this.props.rootNavigation.navigation.navigate('Home').

I don't think this is the optimal solution, just a quick fix.

@temitope

This comment has been minimized.

temitope commented Feb 17, 2017

I had this issue and found that the ideal way to navigate between nested navigators is via the dispatch method where you can have a nested sub-navigation action. @zoontek @goncalomarques


                this.props.navigation.dispatch(
                  {
                      type: 'Navigation/NAVIGATE',
                      routeName: 'GoToANavigator',
                      action: {
                        type: 'Navigation/NAVIGATE',
                        routeName: 'GoToAScreenInANavigator',
                      }
                  }
                 );
@koenpunt

This comment has been minimized.

Contributor

koenpunt commented Feb 24, 2017

This works totally fine if you want to go back from a nested navigator:

navigation.dispatch({ type: 'Navigation/BACK' })

Edit this works when on the first page of the nested navigator, so not a complete solution..

@bjrn

This comment has been minimized.

bjrn commented Mar 3, 2017

I think I've bumped into the same issue as you. The about navigation.dispatch works for me too, but I found an interesting section in the documentation:

[..] If the goal is to go back anywhere, without specifying what is getting closed, call .goBack(null);

navigation.goBack(null); works at least in my use case.

@Kerumen

This comment has been minimized.

Contributor

Kerumen commented Mar 11, 2017

Just a heads up for @goncalomarques answer:

You can inline the nested navigator instead of making it a class:

const AppNavigator = StackNavigator({
  Home: { screen: HomeComponent },
  Nested: { screen: ({ navigation }) => <DashboardNavigator screenProps={{ rootNavigation: navigation }} /> }
});

And the right call to navigate is: this.props.screenProps.rootNavigation.navigate('Home').

@kylecrouse

This comment has been minimized.

kylecrouse commented Mar 29, 2017

@Kerumen When using your example and dispatching a navigate action to a parent navigator from one nested navigator and including a sub-action to navigate to the non-initial screen of a sibling nested navigator, the sub-action appears to be ignored.

Example navigators:

const MainNavigator = StackNavigator({
  screenOne: {
    screen: MainOneComponent
  },
  screenTwo: {
    screen: MainTwoComponent
  },
};

const NestedNavigator = StackNavigator({
  screenOne: {
    screen: NestedOneComponent
  },
  screenTwo: {
    screen: NestedTwoComponent
  },
};

const AppNavigator = StackNavigator({
  main: {
    screen: ({ navigation, screenProps }) => <MainNavigator screenProps={{ parentNavigation: navigation, ...screenProps }} />
  },
  nested: {
    screen: ({ navigation, screenProps }) => <NestedNavigator screenProps={{ parentNavigation: navigation, ...screenProps }} />
  },
});

Example dispatch action from header of one of the nested navigator's child components:

class MainOneComponent extends Component {
  static navigationOptions = {
    header: ({ state }) => ({
      right: (
        <Button onPress={() => {
          let navigationAction = NavigationActions.navigate({
            routeName: 'nested',
            action: NavigationActions.navigate({ routeName: 'screenTwo' })
          });
          state.params.parentNavigation.dispatch(navigationAction);
        }}/>
      )
    })
  }
...
}

The resulting screen displayed is nested->screenOne, not nested->screenTwo as expected.

If AppNavigator is modified to include NestedNavigator directly instead of via function, screenTwo displays as expected but I lose the ability to call the parentNavigator since it isn't getting passed down as a screenProp.

This results in the correct transition (but lacks parentNavigation prop in nested's children):

const AppNavigator = StackNavigator({
  main: {
    screen: ({ navigation, screenProps }) => <MainNavigator screenProps={{ parentNavigation: navigation, ...screenProps }} />
  },
  nested: {
    screen: NestedNavigator
  },
});

I tried another approach where I passed navigation as a prop on the NestedNavigator component within the function, but that resulted in an error that I haven't yet been able to trace:

const AppNavigator = StackNavigator({
  main: {
    screen: ({ navigation, screenProps }) => <MainNavigator screenProps={{ parentNavigation: navigation, ...screenProps }} />
  },
  nested: {
    screen: ({ navigation, screenProps }) => <NestedNavigator navigation={navigation} screenProps={{ parentNavigation: navigation, ...screenProps }} />
  },
});

image

Any thoughts on what I might be missing that will pass the sub-action through properly?

@Kerumen

This comment has been minimized.

Contributor

Kerumen commented Mar 30, 2017

@kylecrouse I stumbled upon the same problem..
Didn't manage to fix it so I reverted my code and declared the screen directly with the component:

const AppNavigator = StackNavigator({
  Nested: { screen: DashboardNavigator }
});
@InteractiveLogic

This comment has been minimized.

InteractiveLogic commented Apr 3, 2017

Hmmm. I've got a bit of a catch-22 here it seems. @goncalomarques -- your solution (and @kylecrouse's) works for me as far as the navigation is concerned but I'm finding that when I do the initial dispatch to navigate to the nested TabNavigator and it's target screen, the params are getting squashed or not passed on.

It's super frustrating.

I think I can get around it by using redux instead of the params, but it's a bit of a hassle for such a small amount of data (one string).

The good news is that calling dispatch() with a reset action on the outer navigation does pop everything back to where I want it. I'm concerned about other side effects too, but this gives me a little hope.

UPDATE: using redux worked fine in my case to get the data to the screen in the nested tab navigator. I'm not sure what other side effects I'll run into on the other screens, but at least I have that working for now.

@ramirobg94

This comment has been minimized.

ramirobg94 commented May 12, 2017

I have also spent hours on this problem, thank you with the idea of using wrapper components.

I have found the following fix using Redux.

//reducerX.js

...
import { NavigationActions } from 'react-navigation';
...
function nav(state=inS, action){
  let nextState;
  switch (action.type) {
      case 'Home':
       homeAction = AppNavigator.router.getActionForPathAndParams('Home');
       nextState = AppNavigator.router.getStateForAction(homeAction);
       break;
    default:
      nextState = AppNavigator.router.getStateForAction(action, state);
      break;
  }
  return nextState || state;
}

....

//nestedComponent
<Button
      onPress={() => this.props.screenProps.parentNavigation.dispatch({ type: 'Home' })}
      title="Home"
    />

I hope it is useful.

@lionhive

This comment has been minimized.

lionhive commented May 24, 2017

I have a similar problem with Presentational/Container ocmponents and navigating.
#1675
What is down side of dispatching actions?

@manuTro

This comment has been minimized.

manuTro commented Jun 22, 2017

Is it possible to nest just stacknavigator?
If so, How can I navigate between them?

@crazyx13th

This comment has been minimized.

crazyx13th commented Jul 13, 2017

Sorry one question... nesting my Navigation is a good solution for now (later I will use Redux-Style), but now the MainScreenNavigator.navigationOptions = { block not working. how can I insert the navigationOptions for the inner MainScreenNavigator ? thx!

 const MainScreenNavigator = TabNavigator({
            Redeem: {screen: RedeemNavigator},
            Statistics: {screen: StatisticsNavigator},
            Settings: {screen: SettingsNavigator},
        });
        MainScreenNavigator.navigationOptions = {
            header: null,
            gesturesEnabled: false
        };

        const RootNavigator = StackNavigator({
            Login: {screen: LoginScreen},
            MainNavigator: {screen: ({ navigation, screenProps }) => <MainScreenNavigator screenProps={{parentNavigation: navigation, ...screenProps }} />}
        }, {
            initialRouteName: 'Login'
        });
@prithsharma

This comment has been minimized.

prithsharma commented Jul 13, 2017

Solutions by @goncalomarques @Kerumen work well for passing rootNavigation from a screen in the root navigator to a nested navigator. While this is a good solution for being able to access rootNavigation.dispatch from screens in a nested navigator.
However, I observed that this doesn't help in passing the rootNavigationState to a nested navigator. That is because every screen of a navigator receives a navigation prop, the state key in this prop contains state of only that screen.

Anyone looking here for a way to pass rooNavigationState might benefit from the following way -

const RootNavigator = StackNavigator({
  screen1: screenNavigator1,
  screen2: screenNavigator2,
});

class RootNavigatorWrapper extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      rootNavState: RootNavigator.router.getStateForAction(NavigationActions.INIT),
    };
  }

  render() {
    return (
      <RootNavigator
        screenProps={{
          rootNavState: this.state.rootNavState,
        }}
        onNavigationStateChange={(prevState, nextState) => {
          this.setState({
            rootNavState: nextState,
          });
        }}
      />
    );
  }
}

With this way, the nested screen navigators(screenNavigator1 and screenNavigator2) would now receive a prop that has the navigation state of the root navigator.

@BenoitClaveau

This comment has been minimized.

BenoitClaveau commented Jul 20, 2017

@ramirobg94 how do you do to set the initialState of the reducer ?

@cadday

This comment has been minimized.

cadday commented Jul 21, 2017

import { NavigationActions } from 'react-navigation'

const navigateAction = NavigationActions.navigate({
  routeName: 'Profile',
  params: {},

  // navigate can have a nested navigate action that will be run inside the child router
  action: NavigationActions.navigate({ routeName: 'SubProfileRoute'})
})
this.props.navigation.dispatch(navigateAction)
// this button is in teams screen and can navigate to loading
<Button title="TO LOADING PAGE"
          onPress={() => {
            const navigateAction = NavigationActions.navigate({
              routeName: "Loading",
              params: {}
            });
            this.props.navigation.dispatch(navigateAction);
          }}
        >
        </Button>


//nested screens
const TeamsStack = StackNavigator({
  Teams: { screen: Teams }
});

const ProfileStack = StackNavigator({
  Profile: { screen: Profile }
});

const NotificationsStack = StackNavigator({
  Notifications: { screen: Notifications }
});

const DrawerNav = DrawerNavigator({
  Main: { screen: TeamsStack },
  Profile: { screen: ProfileStack },
  NotificationsStack: { screen: NotificationsStack }
});

const BackgroundColors = StackNavigator(
  {
    Loading: { screen: LoadingScreen },
    Auth: { screen: Auth },
    Main: { screen: DrawerNav }
  },
  {
    headerMode: "none"
  }
);

https://reactnavigation.org/docs/navigators/navigation-prop#dispatch-Send-an-action-to-the-router

able to navigate from child to parent. for example from teams screen to loading. have not tried parent to child.
this worked for me.

@danstepanov

This comment has been minimized.

danstepanov commented Jul 27, 2017

@goncalomarques Shouldn't DashboardNavigator be NestedNavigator?

@Tom29

This comment has been minimized.

Tom29 commented Aug 10, 2017

@goncalomarques I'm in the same situation like you. One wrapper for TabNavigator, the root is StackNavigator
what if i want navigate from Home (root) to ScreenTwo of Nested ?

@spencercarli

This comment has been minimized.

Member

spencercarli commented Aug 28, 2017

Hi! In an effort to get the hundreds of issues on this repo under control I'm closing issues tagged as questions. Please don't take this personally - it's simply something we need to do to get the issues to a manageable point. If you still have a question I encourage you to re-read the docs, ask on StackOverflow, or ask on the #react-navigation Reactiflux channel. Thank you!

@TheMarsGuy

This comment has been minimized.

TheMarsGuy commented Nov 25, 2017

@bjrn I wonder why they say

go back anywhere

in the doc. What does the anywhere signify?

@manjeetwadi

This comment has been minimized.

manjeetwadi commented Dec 13, 2017

@crazyx13th are you able to find solution for your issue, I am facing almost same as you.

@crazyx13th

This comment has been minimized.

crazyx13th commented Dec 19, 2017

@manjeetwadi sorry, I'm at vacation :-) Maybe my wrapper was the solution.. long time ago :-)

const MainScreenNavigator = TabNavigator({
		RedeemNavigator: {screen: RedeemNavigatorWrapper},
		StatisticsNavigator: {screen: StatisticsNavigator},
		SettingsNavigator: {screen: SettingsNavigator}
	}
	, {
		tabBarOptions: {
			activeTintColor: '#000000',
			labelStyle: Platform.OS === 'ios' ? {
				marginTop: -3,
				marginBottom: 3
			} : {}
		}
	}
);

class MainScreenNavigatorWrapper extends Component
{
	static navigationOptions = ({navigation, screenProps}) => ({
		header: null,
		gesturesEnabled: false
	});

	render()
	{
		const {navigation, screenProps} = this.props;
		return (
			<MainScreenNavigator onNavigationStateChange={null} screenProps={{rootNavigation: navigation, ...screenProps}}/>
		);
	}
}
@manjeetwadi

This comment has been minimized.

manjeetwadi commented Dec 19, 2017

@crazyx13th thanks for the response, actually I tried this way and it works perfectly but we lost control to navigationOptions at screen level in that approach (like try to set tabBarVisible:false)

@e-nouri

This comment has been minimized.

e-nouri commented Dec 29, 2017

@temitope I think the cleanest solution I found is to set the initialRouteName params and then to call the head of the nested stack and it will route automatically to the initialRouteName defined earlier. In my case this is what i used because I split my Screens "Tree" in 2 branches, beforeAuth and afterAuth. Here is an example:

const afterAuthRoutes = {
    Home: {
        screen: HomeScreenStack
    },
    History:{
        screen: AfterAuthScreens.HistoryScreen
    },
...
}

const TabConfig = {
    initialRouteName: 'Home',
...
}

const afterAuth = TabNavigator(afterAuthRoutes, TabConfig)


const beforeAuthRoutes = {
    Login: {
        screen: AuthScreens.LoginScreen,
        path: 'auth/login'
    },
    Signup: {
        screen: AuthScreens.SignUpScreen,
        path: 'auth/signup'
    },
...
}

const authConfig = {
     initialRouteName: 'Login',
...
}

const beforeAuth = StackNavigator(beforeAuthRoutes, authConfig)

const AppNav = StackNavigator({
    authStack: { screen: beforeAuth },
    mainStack: { screen: afterAuth }
  }, {
    // Default config for all screens
    headerMode: 'none',
    title: 'Main',
    initialRouteName: 'authStack'
  })


And now to login the user, I just call navigate("mainStack") to logout the user, navigate("authStack"), ofc by logout and login I mean changing the screens, I am not talking about the entire process.

Hope this will help.

@walter211

This comment has been minimized.

walter211 commented Jan 2, 2018

@mustafatavbatir how do you control the drawer?

@cadday

This comment has been minimized.

cadday commented Jan 2, 2018

@walter211 switched to react-native-navigation long time ago, sorry:(

@ahmad2smile

This comment has been minimized.

ahmad2smile commented Jan 10, 2018

@goncalomarques I was just about to kill myself. thanks your solution worked.

@Andruschenko

This comment has been minimized.

Andruschenko commented Jan 11, 2018

@Kerumen but when you reverted your code to have sub-actions working again, the rootNavigation is not passed down anymore and thus the problem of this question is not fixed.. How did you go about that then?

@uri3000

This comment has been minimized.

uri3000 commented Jan 21, 2018

@Kerumen this line is not working for me in the nested navigator - this.props.screenProps.rootNavigation.navigate('Home')

I managed to get the call working only when calling the screenProps directly, without a preceding this.props: screenProps.rootNavigation

Could anyone please explain why it only works this way?

I haven't defined a new component though, this is how I passed through the rootNavigator:

const SignupWizard = StackNavigator({
  SignUpPersonalDetails: {
    screen: SignUpPersonalDetails
  },
  SignUpLinkAccount: {
    screen: SignUpLinkAccount,
  },
});

and then in render():
<SignupWizard screenProps={rootNavigation = this.props.navigation} />

@shekharskamble

This comment has been minimized.

shekharskamble commented Feb 27, 2018

@goncalomarques - How do I pass extra props to NestedNavigatorWrapper? besides root navigation I need to pass extra data to DashboardNavigator from AppNavigator Please help

@Nickersoft

This comment has been minimized.

Nickersoft commented Mar 6, 2018

@uri3000 screenProps is supposed to be an object. All you're doing in that code is assigning a value to the rootNavigation variable. It should look like this:

<SignupWizard screenProps={{ rootNavigation: this.props.navigation }} />
@brentvatne

This comment has been minimized.

Member

brentvatne commented Mar 6, 2018

we're going to add an escape hatch for this with this.props.navigation.dangerouslyGetParent() -- more info at react-navigation/rfcs#27

@peni4142

This comment has been minimized.

peni4142 commented Apr 6, 2018

My Solution to resolve all accessing problems:

NavigationService = null;

export function getNavigationService() {
    if (this.NavigationService == null) {
        this.NavigationService = new NavigationService();
        return this.NavigationService;
    }
    else {
        return this.NavigationService;
    }
}

class NavigationService {
    constructor() {

    }
    navigations = {};

    addNavigation(key, navigation){
        if(navigation[key] === undefined){
            this.navigations[key] = navigation;
        }
        else{
            //console.log(key  + ' already exists in navigation')
        }
    }

    navigate(nameOfNavigation, navigateTo, params){
        console.log(nameOfNavigation + '\n' + navigateTo + '\n' + params)
        navigation = this.navigations[nameOfNavigation];
        if(params == null | params == undefined){
            navigation.navigate(navigateTo);
        }
        else{
            navigation.navigate(navigateTo, params);
        }
        
    }
    
} 

@keech

This comment has been minimized.

keech commented Apr 15, 2018

@peni4142 I would like to know how to use this. Could you give more information?

@microcipcip

This comment has been minimized.

microcipcip commented Apr 30, 2018

The solutions above are overly complicated! If you are using Redux, you can store a reference with a middleware and use it anywhere, no matter the nesting...

STEP 1: create a redux storeRef middleware

// storeRefHandler.js
import { refStore } from './store' // refStore is just an object...

export default () => {
  return next => action => {
    switch (action.type) {
      case 'STORE_REF':
        refStore[action.refName] = action.ref
        break
    }
    return next(action)
  }
} 

STEP 2: add the middleware to your redux store

// store.js
import refHandler from './storeRefHandler'
const store = createStore(
  rootReducer,
  applyMiddleware(refHandler),
)

// export the refStore object so that it can be accessed anywhere
export const refStore = {} 

STEP 3: create the action

// actions.js
export const storeRef = ({ ref, refName }) => ({
  type: 'STORE_REF',
  refName,
  ref,
})

STEP 4: there is no step 4, you are done!

Usage:

// first we store the navigation we want to use, for example from our MyRootComponent....
import { connect } from 'react-redux'
import { storeRef } from './actions'
componentDidMount () {
  this.props.storeRef({
    refName: 'rootNavigation',
    ref: this.props.navigation
  })
}

function mapDispatchToProps (dispatch) {
  return { storeRef: ({ ...props }) => dispatch(storeRef({...props})) }
}
export default connect(null, mapDispatchToProps)(MyRootComponent)

// now we can use it anywhere!
import { refStore } from 'src/store'
class MyAwesomeComponent extends Component {
  render () {
    return (
      <Button
        onPress={() => refStore.rootNavigation.navigate('Welcome')}
        title="Go to Welcome screen"/>
    )
  }
}
@Friendly-Robot

This comment has been minimized.

Friendly-Robot commented May 12, 2018

                this.props.navigation.dispatch(
                  {
                      type: 'Navigation/NAVIGATE',
                      routeName: 'GoToANavigator',
                      action: {
                        type: 'Navigation/NAVIGATE',
                        routeName: 'GoToAScreenInANavigator',
                      }
                  }
              );

This method works but because it's not dispatched from within a NavigationActions.reset(), the StackNavigator still affects the goBack() functionality that is expected of the DrawerNavigator.

In order to address the issue of removing the Navigator from the Stack using reset, try navigating to the nested route inside of a reset():

    const resetAction = NavigationActions.reset({
      index: 0,
      actions: [NavigationActions.navigate({ 
        routeName: 'DrawerNavigator',
        action: {
          type: 'Navigation/NAVIGATE',
          routeName: 'NestedRoute',
        }
      })]
    })
    this.props.navigation.dispatch(resetAction)
@tiann

This comment has been minimized.

tiann commented Jun 1, 2018

dangerouslyGetParent() can get the parent navigator's navigation property.

StackNavigator
    Home
    Login
    Authenticated -> CustomTabNavigator
        ScreenOne -> StackNavigator
            ScreenOneDetails
            ScreenOneSettings
        ScreenTwo

If you want to ScreenOneDetails -> Login,

this.props.navigation.dangerouslyGetParent().dangerouslyGetParent().navigate('Login')

@adarshashokplr

This comment has been minimized.

adarshashokplr commented Jul 13, 2018

I found an easy solution
use withNavigation is a higher order component which passes the navigation prop into a wrapped Component.

example : https://stackoverflow.com/a/51333660/2849146

for more details : https://reactnavigation.org/docs/en/connecting-navigation-prop.html

@fanlion

This comment has been minimized.

fanlion commented Oct 29, 2018

Navigating without the navigation prop。also can resolve this question。

Calling functions such as navigate or popToTop on the navigation prop is not the only way to navigate around your app. As an alternative, you can dispatch navigation actions on your top-level navigator, provided you aren't passing your own navigation prop as you would with a redux integration. The presented approach is useful in situations when you want to trigger a navigation action from places where you do not have access to the navigation prop, or if you're looking for an alternative to using the navigation prop.

You can get access to a navigator through a ref and pass it to the NavigationService which we will later use to navigate. Use this only with the top-level (root) navigator of your app.

// App.js

import NavigationService from './NavigationService';

const TopLevelNavigator = createStackNavigator({ /* ... */ })

class App extends React.Component {
  // ...

  render() {
    return (
      <TopLevelNavigator
        ref={navigatorRef => {
          NavigationService.setTopLevelNavigator(navigatorRef);
        }}
      />
    );
  }
}

In the next step, we define NavigationService which is a simple module with functions that dispatch user-defined navigation actions.

 // NavigationService.js

import { NavigationActions } from 'react-navigation';

let _navigator;

function setTopLevelNavigator(navigatorRef) {
  _navigator = navigatorRef;
}

function navigate(routeName, params) {
  _navigator.dispatch(
    NavigationActions.navigate({
      routeName,
      params,
    })
  );
}

// add other navigation functions that you need and export them

export default {
  navigate,
  setTopLevelNavigator,
};

Then, in any of your javascript modules, just import the NavigationService and call functions which you exported from it. You may use this approach outside of your React components and, in fact, it works just as well when used from within them.

  // any js module
import NavigationService from 'path-to-NavigationService.js';

// ...

NavigationService.navigate('ChatScreen', { userName: 'Lucy' });

In NavigationService, you can create your own navigation actions, or compose multiple navigation actions into one, and then easily reuse them throughout your application. When writing tests, you may mock the navigation functions, and make assertions on whether the correct functions are called, with the correct parameters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment