Skip to content
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

Choose transition mode for each screen in StackNavigator #707

Closed
ragnorc opened this issue Mar 16, 2017 · 32 comments
Closed

Choose transition mode for each screen in StackNavigator #707

ragnorc opened this issue Mar 16, 2017 · 32 comments

Comments

@ragnorc
Copy link

ragnorc commented Mar 16, 2017

I migrated over from react-native-router-flux. There it was possible for each screen to define whether it should transition vertically or horizontally. I have not seen any easy and quick way to do this like with router-flux until now.

@aimak
Copy link

aimak commented Mar 16, 2017

This is (probably ?) related to #690

@ericvicenti
Copy link
Contributor

This is a super common question and it isn't easy to fix properly, but here is a hack that you may enjoy.

First, you'll need this:

const MultiNavigator = (routeConfigs) => {
  const modalNavigatorRoutes = {};
  Object.keys(routeConfigs).forEach(routeName => {
    const InnerNavigator = StackNavigator(routeConfigs);
    modalNavigatorRoutes[routeName+'Modal'] = { screen: InnerNavigator };
  });
  return StackNavigator(modalNavigatorRoutes, {mode: 'modal'});
};

Now, you can use this new magical thing:

const MyApp = MultiNavigator({
  Home: {screen: HomeScreen},
  Profile: {screen: ProfileScreen},
});

Now, inside your app you can use the normal routes, like navigate('Profile'), but you can also push any screen into the modal navigator, with navigate('ProfileModal').

I'm going to close this out, but if you have a suggestion on an alternative API, feel free to open a proposal as another issue.

@dickfickling
Copy link

@ericvicenti I think there's a bug in your code above.. calling navigate('ProfileModal'), I get a StackNavigator pushed that defaults to the Home screen. I'm currently on beta 7 - maybe something's changed in the commits since, but logically I'd expect the Home screen to be pushed.

@tomav
Copy link

tomav commented Apr 15, 2017

Same as @richardfickling

@tdurand
Copy link

tdurand commented Apr 24, 2017

I get the same bug @richardfickling , did you find a workaround ?

@richardszalay
Copy link

In terms of API, how about adding mode to screen options? That way each screen can override the default as needed.

(to be fair, I've not gone through the transitioner / stacknavigator code, so access to screen options may not be possible)

@hdsenevi
Copy link

hdsenevi commented May 8, 2017

Hi @richardfickling / @tomav / @tdurand. I have the same issue as you guys. Anyone got a solution to this?

@dickfickling
Copy link

const MainCardNavigator = StackNavigator(
  {
    Home: { screen: Home },
    CardScreen1: { screen: CardScreen1 },
    CardScreen2: { screen: CardScreen2 },
  },
  {
    headerMode: 'none',
  },
);

const MainModalNavigator = StackNavigator(
  {
    MainCardNavigator: { screen: MainCardNavigator }
    ModalScreen1: { screen: ModalScreen1 },
    ModalScreen2: { screen: ModalScreen2 },
  },
  {
    mode: 'modal',
    headerMode: 'none',
  },
);

I display the MainModalNavigator, and can navigate from HomeScreen to any of the card or modal screens, with the correct transition. Only gotcha is that a screen displayed modally can't navigate to a screen displayed as a card.

Sorry, something went wrong.

@ghost
Copy link

ghost commented May 9, 2017

Is anyone else here getting a back button in their modals using the nesting configuration shown by @richardfickling?

Sorry, something went wrong.

@dickfickling
Copy link

Oh yeah, headers are likely messed up. I have my own custom header throughout my app, so I've never had that issue

@tdurand
Copy link

tdurand commented May 9, 2017

Something like that @antgly

import { HeaderBackButton } from 'react-navigation';


static navigationOptions = ({ navigation }) => ({
    headerLeft: <HeaderBackButton onPress={() => navigation.dispatch({ type: 'Navigation/BACK' })} />,
    headerTitle: "Title"
})

@ghost
Copy link

ghost commented May 9, 2017

@tdurand I have the opposite issue.

@wandonye
Copy link

@richardfickling In the code below, if you set headerMode to be none then there won't be any header. How would you navigate back?

const MainCardNavigator = StackNavigator(
  {
    Home: { screen: Home },
    CardScreen1: { screen: CardScreen1 },
    CardScreen2: { screen: CardScreen2 },
  },
  {
    headerMode: 'none',
  },
);

@dickfickling
Copy link

I have my own custom header on every screen, so I use headerMode: none on all of my navigators

@maksimdegtyarev
Copy link

maksimdegtyarev commented May 10, 2017

@richardfickling how did you connect your custom header with navigator? Or you mean something like this:

<View>
  <Header />
  <ScrollView>
   ...
  </ScrollView>
</View>

@dickfickling
Copy link

yeah, it's basically what you said. In my screen's view, <Header navigation={this.props.navigation} /> and in the header I have a <Button onPress={() => this.props.navigation.goBack()} /> (not the cleanest code, but the world will keep spinning regardless)

@wandonye
Copy link

With 1.0-beta9, I set headerMode to be screen in the navigator, and customize each screen with static headerOption= {...}. I can also disable header of any screen with header: null. But I think @richardfickling 's solution can solve issue #145 at the same time.

@wandonye
Copy link

wandonye commented May 10, 2017

@richardfickling Could you pls give more details on the usage of <Header>? With the code below

        <Header navigation={this.props.navigation}>
          <Button title='Back' onPress={() => this.props.navigation.goBack()} />
        </Header>

I got

Cannot read property 'index' of undefined
TypeError: Cannot read property 'index' of undefined

@gregblass
Copy link

gregblass commented May 18, 2017

The modal workaround above from @ericvicenti doesn't work. It opens up the home screen instead of the screen that you specify. Also, it ignores navigation options - although I'm sure you were just giving an example and you could the example code to apply them.

@gregblass
Copy link

@richardfickling Your workaround works wonderfully! Thank you! That should be the goto solution. The issue of not being able to navigate again through the card stack from there is not important to my use case because I'm just loading one off modals.

@theebi
Copy link

theebi commented May 26, 2017

Hello,
I have a modal working fine. I have made the modal window transparent. But I have to reduce the opacity of background screen when modal is presented. Any workaround on this?

@cwagner22
Copy link

The solution from @richardfickling works great but it's cumbersome to have to manually add headerLeft to each screen. I can add it only once to the StackNavigator default settings:

StackNavigator({
...
}, {
  headerMode: 'none',
  navigationOptions: ({navigation}) => ({
    headerLeft: <HeaderBackButton title='Back' onPress={() => navigation.dispatch({ type: 'Navigation/BACK' })} />
  })
})

But then the back button will always have the text 'Back' instead of the previous screen name.

@davidsamacoits
Copy link

Even if @richardfickling's solution fixed the problem, I now have a bigger one ! I have my screens configured like this :

// Card Navigation
//
const CardNavigator = StackNavigator({
  // Home: { screen: TabNav },
  Discover: {
    screen: Discover,
    navigationOptions: () => ({
      title: 'Discover',
    }),
  },
  DetailShow: {
    screen: DetailShow,
    navigationOptions: () => ({
      headerMode: 'none',
    }),
  },
}, { headerMode: 'screen' });

// Main Navigation (modal)
//
export const AppNavigator = StackNavigator({
  Home: {
    screen: CardNavigator,
  },
  CustomModal: {
    screen: CustomModal,
    navigationOptions: () => ({
      headerMode: 'none',
    }),
  },
}, { headerMode: 'none', mode: 'modal'});

When I navigate to CustomModal everything works. I have a nice route from Home to CustomModal.

But when I navigation to DetailShow... I get my screen, but when I look at the route I get only one element : Home

That's a real problem because it means you can't use .goBack() and it'd close your app if you use it on an Android device.

Any idea?

@richardgirges
Copy link

richardgirges commented Aug 5, 2017

I really like @richardfickling's and @ericvicenti's approach, so this is what I did to get it working on my end (essentially combined both of their approaches).

FULL DISCLOSURE: Not fully tested yet, haven't tried it out with headers. Will post another update as this evolves.

const StackModalNavigator = (routeConfigs, navigatorConfig) => {
  const CardStackNavigator = StackNavigator(routeConfigs, navigatorConfig);
  const modalRouteConfig = {};
  const routeNames = Object.keys(routeConfigs);

  for (let i = 0; i < routeNames.length; i++) {
    modalRouteConfig[`${routeNames[i]}Modal`] = routeConfigs[routeNames[i]];
  }

  const ModalStackNavigator = StackNavigator({
    CardStackNavigator: { screen: CardStackNavigator },
    ...modalRouteConfig
  }, {
    mode: 'modal',
    headerMode: 'none'
  });

  return ModalStackNavigator;
};

Create the navigator:

const RootNavigator = StackModalNavigator({
  Intro: {
    screen: Intro
  },
  Profile: {
    screen: Profile
  },
  Browse: {
    screen: Browse
  }
}, {
  headerMode: 'none'
});

// ...

<RootNavigator />

Usage:

// Navigate with regular transition
this.props.navigate('Profile')

// Navigate with modal transition (just append "Modal" to the end of the route name)
this.props.navigate('ProfileModal')

@gontovnik
Copy link

I had the issue with the @richardgirges's implementation when I had nested StackModalNavigators. The solution was to have a custom name for CardStackNavigator inside ModalStackNavigator:

const StackModalNavigator = (name, routeConfigs, navigatorConfig) => {
  const CardStackNavigator = StackNavigator(routeConfigs, navigatorConfig);
  const modalRouteConfig = {};
  const routeNames = Object.keys(routeConfigs);

  for (let i = 0; i < routeNames.length; i++) {
    modalRouteConfig[`${routeNames[i]}Modal`] = routeConfigs[routeNames[i]];
  }

  const ModalStackNavigator = StackNavigator(
    {
      [name]: { screen: CardStackNavigator },
      ...modalRouteConfig
    },
    {
      mode: 'modal',
      headerMode: 'none'
    }
  );

  return ModalStackNavigator;
};

@benevbright
Copy link
Contributor

benevbright commented Dec 19, 2017

Guys. here is what you want.

And do like this when you want to go to modal and back.

        screenProps.modalNavigation.navigate('modalmain');
const _NavMain = StackNavigator({
    root:{ screen: MainScreen },
    stackdepth1:{screen: stackdepth1},
    stackdepth2:{screen: stackdepth2},
}
, {navigationOptions:whatever})

const _NavModalMain = StackNavigator({
    modalroot:{ screen: modalroot, navigationOptions:(props) => ({
        headerLeft:
            <TouchableOpacity onPress={() => {props.screenProps.modalNavigation.goBack()}}>
                <Text style={{color:'white', fontWeight:'bold', marginHorizontal:15}}>Close Modal</Text>
            </TouchableOpacity>
    })}
}
, {navigationOptions:whatever})

const Main = StackNavigator({
    stackmain:{ screen: ({navigation}) => <_NavMain screenProps={modalNavigation:navigation}/>  },
    modalmain:{ screen: ({navigation}) => <_NavModalMain screenProps= screenProps={modalNavigation:navigation}/> },
}
, {navigationOptions:whatever, headerMode:'none', mode:'modal'})

@bradbyte
Copy link

@richardgirges @gontovnik thank you for this great implementation! It worked great for iOS, and now trying to port my app over to Android and finding that all routes are transitioning like a modal. Have you experienced this as well?

@xiaogliu
Copy link

xiaogliu commented Feb 5, 2018

@bradbumbalough i think that is not a problem, Android default screen transition just like a modal

@RobPando
Copy link

RobPando commented Feb 9, 2018

@bradbumbalough @xiaogliu You have to manually declare the transitionConfig for the StackNavigator you have intended for mode: 'card'

import { StackNavigator } from 'react-navigation'
import CardStackStyleInterpolator from 'react-navigation/src/views/CardStack/CardStackStyleInterpolator'

const CardStackNavigator = StackNavigator(
  {
    Home: { screen: Home },
    Screen1 : { screen: Screen1 },
  },
  {
    initialRouteName: 'Home',
    headerMode: 'none',
    transitionConfig() {
      return { screenInterpolator: CardStackStyleInterpolator.forHorizontal }
    },
  },
)

However, my problem with this nested stack navigator solution to have both modal and card mode is that for Android, it is horribly slow in transition to reset a route from a nested stack to the root stack. In my case I have a root stack with mode: 'modal' and inside a nested stack with mode: 'card'. when I navigate to a nested card view and then another one (now 2 nested card screens in the stack) and then try and reset the route back to Home in the root stack navigator. It is painfully slow and buggy, it is honestly beyond me how a solution for this has not been prioritized as mentioned by the maintainer it is a super common problem. I love this community but I feel like it neglects Android so much, when 80% of the market is Android.

@tpraxl
Copy link

tpraxl commented Mar 1, 2018

Does anyone have a nice recipe for a Modal in an App that uses TabRouter like so:

const Main = TabRouter(
  {
    Screen1: {
      screen: Screens.One,
    },
    Screen2: {
      screen: Screens.Two,
    },
},
  {
    initialRouteName: 'Screen1',
  });

const MyApp = createNavigationContainer(createNavigator(Main)(AppScreen));

The approach with two nested "Navigators" does not work here as far as my tests went. I cannot add TabRouter as a child to the root Navigator. I'm currently stuck. Anyone?

@brentvatne
Copy link
Member

make the tabnavigator a child of a stacknavigator

const MainTabs = TabNavigator({ /* your routes etc */ });
const MyApp = StackNavigator({
  TabThing: MainTabs,
  ModalThing: SomeScreenOrStack,
}, {
  mode: 'modal',
  initialRouteName: 'TabThing',
});

@brentvatne
Copy link
Member

I'm going to lock this issue because it's not a useful place to continue discussions. Feel free to comment further on here: react-navigation/rfcs#10

@react-navigation react-navigation locked and limited conversation to collaborators Mar 6, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests