react-navigation / rfcs

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

What should happen when passing params to a navigator route? #43

Open brentvatne opened 6 years ago

brentvatne commented 6 years ago

Related issues:

liuqiang1357 commented 6 years ago

I don't think the getParentParam is necessary, because if we passed params to child navigator, we can reference the params by dangerouslyGetParent().getParams in the child screen, and we also can wrap the navigator to decide how to use the params for all the child screen (see pull request below).

But at first we should let the child navigator to receive params. There is some issue in current version and I have made a pull request #4306 .

mtnt commented 6 years ago

Since it is react-navigation I guess params should be converted to props. It should be passed to a target component only. All further props-params manipulations in the component is up to a developer.

Since I believe the navigator should be able to made as a HOC (without any scene changed inside itself), params should be available to be taken in navigationOptions, for example, for a header configuration.

brentvatne commented 6 years ago

@Mtnt - they are props. this.props.navigation.state.params. that's not really what this issue is about though. the question here is how we should deal with params when they are passed into a navigator, so if you navigate to a screen that is just a react component vs a screen that is a navigator, would params be passed through to the screen rendered by the navigator or not?

mtnt commented 6 years ago

@brentvatne I meant first level props - this.props.param0, but ok, I`ll open another one issue for this point.

About your comment. As I already said, I think params should be passed into a navigation target screen only (without passing through). What to do with it further is the screen responsibility.

brentvatne commented 5 years ago

this causes a lot of confusion - https://github.com/react-navigation/react-navigation/issues/4811

we should document this.props.navigation.dangerouslyGetParent().getParam(...) as a way to get the params from the parent, or provide a method that gets params from the parent without the word dangerously maybe...

Titozzz commented 5 years ago

I have a use case where It would be helpful to me that any screen / navigator under a certain navigator gets a common param, and I really believed this would be the default behavior (passing down all params with the child always having priority when param names conflict).

I will use a workaround by extending navigator and using react context in the meantime 😄

slorber commented 5 years ago

Hi @Titozzz

If you need to build some kind of multiscreen stateful wizzard you can share state across those screens by wrapping the navigator.

MyStatelessStackNavigator = createStackNavigator({})

// Forward the prop
MyStatefulStackNavigator = ({navigation}) => (
   <MyStateProvider>
      <SomeFancyWizzardStepHeader/>
      <MyStatelessStackNavigator navigation={navigation}/>
   </MyStateProvider>
);

// Very important otherwise MyStatefulStackNavigator is not considered as a navigator
MyStatefulStackNavigator.router =  MyStatelessStackNavigator.router; 

export default MyStatefulStackNavigator;

This is a common pattern where you just wrap a navigator inside custom one to enhance it. More infos here: https://reactnavigation.org/docs/en/custom-navigators.html

Unlike global state based solutions, this one will actually reset its state when you navigate away from this navigator, which makes it more suitable for transcient state like multi-screen forms/wizzards... I think we should do a better job at documenting this kind of patterns with react-navigation. Will probably write a blog about it soon.

Note you might be tempted to try to use "screenProps" here but actually this should only be used on root app container, and after discussing with Satya last year he suggested using context is the way to go for this kind of usecase, and maybe screenProps would be deprecated some day.

Note if you are looking for an easy to use state provider to make your stack navigation stateful, I think unstated is a very decent option and anyone knowing React will understand how to build features with this very easily. Once it's setup you almost get no learning curve. Only difference is setState being async (which imho is a nice addition).

And if you don't need a stateful wrapper, you can still use the trick above and add a simple React context to propagate a static value to all subscreens.

Titozzz commented 5 years ago

Using react context wrapping my navigator is exactly what I did, sorry I did not give more details in my above message 😄. And once hooks land in stable react-native it'll get even cleaner to use.

bhave-abhay commented 5 years ago

I was struggling with the same, and after almost a week of frustration finally used context and got what I wanted, demonstrated in https://github.com/react-navigation/react-navigation/issues/5646

I think the mention of possibilities with the use of cotext api (though it is in parent platform i.e. react) in react-navigation documentation would help a lot.

Should we / I open an issue for this on documentation site repo?

brentvatne commented 5 years ago

@bhave-abhay - a docs page for that could be really great! you would probably be a great person to write it given the experience you've had, can you open a pr for it?

SaeedZhiany commented 5 years ago

@brentvatne Let's assume a routing like below:

DrawerNavigator:
|______ StackNavigator
          |______ Screen 1
          |______ TabNavigator
                    |_______ Tab 1 <--- initial route
                    |_______ Tab 2
                    |_______ Tab 3

One scenario for the routing is an app that shows a list of items in Screen 1 and shows an item's detail when the user pressed on the item. because of the massive amount of information, the developer decides to show the information in three tabs (price tab, technical info, and contact tab), So the tabs are related, also, assume that the developer should fetch the data by sending 3 different requests to a server (in the scenario I'm thinking about the tabs are able to work completely separated from each other, in other words, although the tabs are showing information of the same Item all of their processes are isolated from each other). in the other hand, the server may expect an ItemId and some other parameters in the request for each tab.

in the current implementation, we should pass all parameters to the TabNavigator and extract the required params in each tab using this.props.navigation.state.params in Tab 1 and this.props.navigation.dangerouslyGetParent().getParams in the other Tabs. it has two cons in my opinion:

  1. If the developer wants to change the order of the tabs, he/she must change the code to retrieve params, because of two different access way to the parameters.
  2. Developers like me may like to convert params to component props to easily accessible in a consistent way (by using something like below)
Tab 1: {
    screen: (props) => (<ScreenWrapper component={ItemOverviewScreen} params={props}/>)
}

Class ScreenWrapper {
    public render() {
        const Component = this.props.component;
        return (
            <Component {...this.props.params}/>
        );
    }
}

Class ItemOverviewScreen {
    // work with props without any concern and knowledge about routings parameter passing
}

Not passing down the params causes we lose the above ability in tab navigators at least.

  1. For some reason, Let assume that the app's routing is changed like below:
DrawerNavigator:
|______ StackNavigator
          |______ Screen 1
          |______ TabNavigator
                    |_______ Tab 1 <--- initial route
                    |_______ Tab 2
                    |_______ Tab 3
          |______ Tab 3 <--- the same screen used in above Tab 3

the developer must check both this.props.navigation.state.params and this.props.navigation.dangerouslyGetParent().getParams to cover both routing in one place.

My suggestion Passing the params of each tab by wrapping them into an object with a key of the corresponding Tab routing name. I mean something like below:

The developer doing navigate to Tab Navigator:

NavigationAction.navigate ({
    routeName: TabNavigator,
    params: {
        Tab 1: {
            // params of Tab 1
        },
        Tab 2: {
            // params of Tab 2
        },
        Tab 3: {
            // params of Tab 3
        },
    }
});

the library should pass only the corresponding params to each Tab in the screen: (props) => .... probably we can extend the suggestion for other Navigators include nested ones.

VBarzionov commented 4 years ago

I need to pass same "userId" param to all Tabs inside Tab.Navigator Doc says there is no "dangerouslyGetParent().getParams" in React Navigation 5 any more. However it is totally unclear how to share/access same "nav-param" for all Tabs inside TabNavigator now. One solution is using external storage (react-context/redux/other).

I found following solution/workaround. Hope it helps somebody.

  <Stack.Screen name="Settings" >
    { (props) => (
      <Tab.Navigator>
        <Tab.Screen name="Tab1" component={ Tab1 } 
          initialParams={ props.route.params }  // <- pass params from root navigator
        />
        <Tab.Screen name="Tab2" component={ Tab2 } 
          initialParams={ props.route.params } 
        />
      </Tab.Navigator>
      )}
  </Stack.Screen>

Navigation params pass from root stack-navigator to all Tabs

byteab commented 4 years ago

@brentvatne getParam is undefined

imavipatel commented 3 years ago

I need to pass same "userId" param to all Tabs inside Tab.Navigator Doc says there is no "dangerouslyGetParent().getParams" in React Navigation 5 any more. However it is totally unclear how to share/access same "nav-param" for all Tabs inside TabNavigator now. One solution is using external storage (react-context/redux/other).

I found following solution/workaround. Hope it helps somebody.

  <Stack.Screen name="Settings" >
    { (props) => (
      <Tab.Navigator>
        <Tab.Screen name="Tab1" component={ Tab1 } 
          initialParams={ props.route.params }  // <- pass params from root navigator
        />
        <Tab.Screen name="Tab2" component={ Tab2 } 
          initialParams={ props.route.params } 
        />
      </Tab.Navigator>
      )}
  </Stack.Screen>

Navigation params pass from root stack-navigator to all Tabs

thank you so much for this. I was trying to solve this problem from last 2 days luckily i reached here