react-navigation / rfcs

RFCs for changes to React Navigation
Other
88 stars 16 forks source link

Add initialRouteName and initialRouteParams props to stack #23

Closed brentvatne closed 4 years ago

brentvatne commented 6 years ago

People generally want this, eg: https://github.com/react-navigation/react-navigation/issues/3518

A proper RFC would be appreciated

brentvatne commented 6 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.

slorber commented 6 years ago

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:

lxcid commented 6 years ago

My 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

dani-mp commented 6 years ago

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.

satya164 commented 6 years ago

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.

harvey-woo commented 5 years ago

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.

dani-mp commented 5 years ago

Hi, @harvey-woo. Can you share a small example with us?

harvey-woo commented 5 years ago

@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>
  }
}
quincycs commented 5 years ago

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

rajeevrocker7 commented 5 years ago

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…

satya164 commented 4 years ago

Closing since React Navigation 5 implements prop-based configuration.