Closed brentvatne closed 4 years ago
Copying over my comment from #458
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 https://reactnavigation.org/docs/auth-flow.html for a full example of this.
I want to add my usecase for this RFC, as providing only initialRouteName
may not be flexible enough for me.
In my app, I have a "global navigation" that is always mounted, and when the user trigger a process, there's some kind of "command wizzard" that is made using a StackNav of multiple screens, one after the other. This StackNav is not always mounted, it gets mounted only when user starts the command process. And the app need the ability to "restore" the command process, which actually means that we should mount the StackNav with a given initial state and multiple stack screens initially, potentially in different orders, and not just having the ability to set the initial root screen.
A workaround to setup an initial StackNav state on mount is the following:
class CommandScreenNavigator extends React.PureComponent {
componentDidMount() {
this.setupInitialNavState();
}
setupInitialNavState = () => {
let initialAction = undefined;
// Read from parent main app navigation
const restoreCommandProcessState = this.props.navigation.state.params.restoreCommandProcessState;
if ( restoreCommandProcessState && restoreCommandProcessState.toScreen === 3 ) {
initialAction = NavigationActions.reset({
index: 2,
actions: [
NavigationActions.navigate({routeName: FirstScreen}),
NavigationActions.navigate({routeName: SecondScreen}),
NavigationActions.navigate({routeName: ThirdScreen}),
],
});
}
else if ( restoreCommandProcessState && restoreCommandProcessState.toScreen === 2 ) {
initialAction = NavigationActions.reset({
index: 1,
actions: [
NavigationActions.navigate({routeName: FirstScreen}),
NavigationActions.navigate({routeName: SecondScreen}),
],
});
}
initialAction && this.stackNavigation.dispatch(initialAction);
};
render() {
const commandWorkflowScreenProps = {...};
return (
<CommandScreenStackNavigation
ref={c => this.stackNavigation = c}
screenProps={commandWorkflowScreenProps}
/>
);
}
}
So, instead of ability to set initialRouteName, I think it would be more powerful to give ability to initialise the state of a stateful StackNav, which would actually permit to set an initialRouteName.
Potential workarounds for setting initialRouteName / initialState for my usecase would be:
addNavigationHelpers
etcMy use case is simple, I have a tab navigator that I wan to launch to the specific screen define in the tab.
This is something possible in UITabBarController in iOS.
In my use case, the switch navigator suggestion doesn’t make sense though
I think the tab navigator example is a good one: when presenting it, you may want to show a different default tab based on the screen props, for instance. Similar with a switch navigator. You could navigate from the outside to the proper screen, but I rather present the navigation with some props and let it decide its initial state.
In a stack navigator, maybe I have a three steps stack (A, B, C) where A contains a list and B is a details screen. I may want to show the navigator either starting from A, where I can fetch some items and show their details, or if I have already an item, I can skip A and show B directly.
IMO we should be able to pass all navigator and router options as normal props. But for the router it will need a lot of refactoring since they are created at declaration time rather than during render.
As @satya164 said, I pass router options as props to navigator, I think this has already met the demand. but I still need some official documentation or feature for this situation.
Hi, @harvey-woo. Can you share a small example with us?
@danielmartinprieto
I just simply create a routerConfig2Navigator
function to convert routerConfig to Navigator component
const type2NavigatorComponentCreater = {
switch: createSwitchNavigator,
bottomTab: createBottomTabNavigator
}
type Component = FunctionComponent | React.ComponentClass
type RouterConfig = {
type: keyof typeof type2NavigatorComponentCreater,
initialRouteName ?: string,
transitionConfig: () => any,
routes: {
[key: string] : RouterConfig | Component
}
} & {
[ otherConfig: string ]: any
}
function isComponent(target: any): target is Component {
return typeof target === 'function'
}
function routerConfig2Navigator(routerConfigOrComponent: RouterConfig | Component, initialRouteNames ?: string[]) {
if (isComponent(routerConfigOrComponent)) {
return routerConfigOrComponent
}
const { routes, type, ...options } = routerConfigOrComponent
let initialRouteName: string | undefined
let subInitialRouteNames: string[]
if (initialRouteNames && initialRouteNames.length) {
[ initialRouteName, ...subInitialRouteNames ] = initialRouteNames
}
const innerRoutes: { [key: string]: any } = {}
Object.keys(routes).forEach(key => {
innerRoutes[key] = routerConfig2Navigator(routes[key], subInitialRouteNames)
})
return type2NavigatorComponentCreater[type](innerRoutes, {
...options,
...(initialRouteName ? {
initialRouteName
}: {})
})
}
const routerConfig: RouterConfig = {
type: 'switch',
initialRouteName: 'Login',
routes: {
Login,
Home: {
type: 'bottomTab',
tabBarComponent: HomeFooterTab,
initialRouteName: 'HomeMy',
routes: {
HomeIndex,
HomeRecord,
HomeMy
}
}
}
}
export default class App extends Component {
render() {
const RouterContainer = createAppContainer(routerConfig2Navigator(routerConfig, ['Login']))
const content = <RouterContainer></RouterContainer>
return <RouterContainer></RouterContainer>
}
}
My 2 cents, this is how I accomplished my desire for a dynamic initial route: https://github.com/quincycs/react-native-q-starter/commit/69f8b7607afc8717bf98a339ec9547e107f1b639
const TabsNav = createBottomTabNavigator({ A: stackA, B: stackB, C: stack C }, { order: ['A', 'B', 'C'], initialRouteName: 'A', //<-- want to change this acc. to “this.props.navigation.getParam('fromWhere', ‘’);” tabBarPosition: 'bottom', });
//say I have a component 'HomeDash'
//SCREEN render() { return ( <HomeDashContainer ref={nav => (this.homeDashRef = nav)} screenProps={{ homeDashNav: this.props.navigation }} /> ); }
const HomeDashContainer = createAppContainer(TabsNav);
export default HomeDash;
// I have required route name in an async method that I called in constructor or UNSAFE_componentWillMount
My requirement is to set initial tab on basis of “this.props.navigation.getParam('fromWhere', ‘’);”
Any running solution will be helpful…
Closing since React Navigation 5 implements prop-based configuration.
People generally want this, eg: https://github.com/react-navigation/react-navigation/issues/3518
A proper RFC would be appreciated