Source:  Twitter logo

Say I've navigated through 4 screens in my StackNavigator App and now I want to go back to the first screen. There seems to be three different ways to do this and they do navigate to where I want to do, however each way has an animation that cycles through each previous screen.

Is there a clean way to navigate from SCREEN_D to SCREEN_A?

In other words, I don't want to see SCREEN_C and SCREEN_B a split of a second before seeing SCREEN_A when navigating backwards from SCREEN_D

navigation.navigate(SCREEN_A);
...
navigation.navigate(SCREEN_B);
...
navigation.navigate(SCREEN_C);
...
navigation.navigate(SCREEN_D);

Three ways to do this:

1.

return this.props.navigation
               .dispatch(NavigationActions.reset(
                 {
                    index: 0,
                    actions: [NavigationActions.navigate({ routeName: 'SCREEN_A'})]
                  }));

2.

 const {SCREEN_B_KEY} = this.props.navigation.state.params
 this.props.navigation.goBack(SCREEN_B_KEY)

3.

const defaultGetStateForAction = Navigation.router.getStateForAction;

Navigation.router.getStateForAction = (action, state) =>{
  if(action.type === "Navigation/BACK"){
    const routes = [
      {routeName: 'First'},
    ];
    return {
      ...state,
      routes,
      index: 0,
    };
  }
  return defaultGetStateForAction (action,state);
}

react-navigation has popToTop and we can use it like blow

this.props.navigation.popToTop()
27 users liked answer #0dislike answer #027
Sultan Ali profile pic
Sultan Ali

Here's a quick fix. This will remove ALL transitions when navigating (forward or backward).

const Nav = StackNavigator({
  Screens
},{
  transitionConfig,
  navigationOptions
});

in transitionConfig add this:

const transitionConfig = () => ({
    transitionSpec: {
      duration: 0,
      timing: Animated.timing,
      easing: Easing.step0,
    },
  })

The transitionConfig is a function that returns an object that overrides default screen transitions. https://reactnavigation.org/docs/navigators/stack

  • if someone knows a way to change animations on single navigation, I'd love to hear how!
7 users liked answer #1dislike answer #17
Turnipdabeets profile pic
Turnipdabeets

You can do this by using the popToTop method from the Navigation prop. Also, if you are using redux, you should update your Redux integration.

Hope this helps!

5 users liked answer #2dislike answer #25
Denny K. Schuldt profile pic
Denny K. Schuldt

For 2020 / react-navigation-stack I use the following code when setting up the StackNavigator:

import { createStackNavigator, CardStyleInterpolators } from 'react-navigation-stack';
import { Easing } from 'react-native';

const Navigator = createStackNavigator({
  // ...
}, {
  defaultNavigationOptions: ({ navigation }) => {
    const animation = navigation.getParam('_animation');
    return {
      // ...
      cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS,
      ...(animation === 'back' && {
        gestureDirection: 'horizontal-inverted',
      }),
      ...(animation === 'skip' && {
        transitionSpec: {
          open: { animation: 'timing', config: { duration: 0, easing: Easing.step1 }, },
          close: { animation: 'timing', config: { duration: 0, easing: Easing.step0 }, },
        },
      }),
    };
  },
});

and the the _animation param to override the animation style

// replace to a route not in the stack normally creates a forward animation, but
// we want a backwards animation
this.props.navigation.dispatch(
  StackActions.replace({ routeName: 'Login', params: { _animation: 'back' } })
);
3 users liked answer #3dislike answer #33
Moritz profile pic
Moritz

Also spent some time on this, let me sum up what I discovered, there are multiple solutions/workarounds for this:

1) Use CardStackStyleInterpolator

The pull request mentioned by Cristiano Santos seems to be merged. So you can load the CardStackStyleInterpolator with this import:

import CardStackStyleInterpolator from 'react-navigation/src/views/CardStack/CardStackStyleInterpolator'

To apply it like this:

const YourStackNavigator = StackNavigator({
    Screen1: { screen: Screen1 },
    Screen2: { screen: Screen2 },
}, {
    transitionConfig: () => ({
        screenInterpolator: (props) => CardStackStyleInterpolator.forHorizontal(props)
    })
});

In my case, I just jump to the next screen like:

this.props.navigation.navigate('Modal_AddGoodClass');

But in my reducer, I reset the navigator when the Modal_AddGoodClass screen is triggered:

const NewExportReceiptNavigationReducer = (state, action) => {
    // Default stuff

    let newStateBuffer = newState || state;

    if (action) {
        if (action.type === 'Navigation/NAVIGATE') {
            if (action.routeName === 'Modal_AddGoodClass') {
                newStateBuffer = {
                    index:  0,
                    routes: [
                        newStateBuffer.routes[newStateBuffer.routes.length - 1]
                    ]
                };
            }
        }
    }

    return newStateBuffer;
};

module.exports = NewExportReceiptNavigationReducer;

This works pretty well, except the fact, that still a "back" animation is used instead of a "forward" one.

You can also find here some example code that uses CardStackStyleInterpolator.

2) Overwrite getStateForAction:

As Fendrian mentioned here you can overwrite getStateForAction of your router to avoid the navigator to go back. This seems to work except for the "swipe back" gesture on iOS:

Nav = StackNavigator(navScreens, navOptions);
const defaultGetStateForAction = Nav.router.getStateForAction;
Nav.router.getStateForAction = (action, state) => {
  if (
    state &&
    action.type === NavigationActions.BACK &&
    (
      state.routes[state.index].routeName === 'Login' ||
      state.routes[state.index].routeName === 'Main'
    )
  ) {
    // Returning null indicates stack end, and triggers exit
    return null;
  }
  return defaultGetStateForAction(action, state);
};

0 users liked answer #4dislike answer #40
Thomas Kekeisen profile pic
Thomas Kekeisen

This is my working solution for reset back to home (root) without creating new route

if(this.categoriesNav.state.nav.index >0){
    let k = this.categoriesNav.state.nav.routes[1].key;
    this.categoriesNav.dispatch(NavigationActions.back({key: k})); }

categoriesNav is referenced to my stack navigator

0 users liked answer #5dislike answer #50
Masoud Teymouri profile pic
Masoud Teymouri

I think I got a solution as described in this article https://medium.com/async-la/custom-transitions-in-react-navigation-2f759408a053 that explains how navigation works pretty well (would strongly recommend reading it as it's a pretty quick read and helped me get a better understanding of how transitions work).

Here's a gif of a sample navigator that has smooth transitions for moving multiple screens back and forth: Navigator Gif (Sorry it's a link, I don't have enough rep yet to embed a video so I had to do a link). However, this shows how the navigator whose source code is below behaves.

Essentially what happens is if you want to go from screen 4 on your stack to 1, you can set the opacity of 2 and 3 to 0, so then the transition will appear to go from 4 to 1.

Here's the source code for the navigator:

import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
import ScreenA from './ScreenA';
import ScreenB from './ScreenB';
import ScreenC from './ScreenC';
import ScreenD from './ScreenD';

const OuterNavigator = createStackNavigator(
{
    ScreenA: {
        screen: ScreenA,
        navigationOptions: {
            //Set header stuff here
            headerTintColor: "#fff",
            headerStyle: {
                backgroundColor: '#4444aa',
            },
            headerTitle: "Screen A"
        }
    },
    ScreenB: {
        screen: ScreenB,
        navigationOptions: {
            headerTintColor: "#fff",
            headerStyle: {
                backgroundColor: '#44aa44',
            },
            headerTitle: "Screen B"
        }
    },
    ScreenC: {
        screen: ScreenC,

        navigationOptions: {
            headerTintColor: "#fff",
            headerStyle: {
                backgroundColor: '#aa4444',
            },
            headerTitle: "Screen C"
        }
    },
    ScreenD: {
        screen: ScreenD,

        navigationOptions: {
            headerTintColor: "#fff",
            headerStyle: {
                backgroundColor: '#44aaaa',
            },
            headerTitle: "Screen D"
        }
    },
},

{
    // Sets initial screen to screen A
    initialRouteName: 'ScreenA',

    // Can be changed to whatever you prefer
    headerMode: 'float',

    // This line makes a transparent background so the navigator can be wrapped in a background image
    // (this was an issue I struggled with and figured there's a change someone else might be as well)
    cardStyle: { backgroundColor: '00000000', shadowColor: '000000' },
    transitionConfig: () => ({
        // This link exlpains this in detail: 
        // https://medium.com/async-la/custom-transitions-in-react-navigation-2f759408a053
        containerStyle: {
            // This also has to do with having a background image
            backgroundColor: '00000000',
        },
        transitionSpec: {
            // Sets speed for transition (lower -> faster)
            duration: 500,
            useNativeDriver: true,
        },
        screenInterpolator: sceneProps => {
            // Parses scene props
            const { position, layout, scene, index, scenes } = sceneProps

            // Grabs height and width of screen
            const toIndex = index
            const thisSceneIndex = scene.index
            const height = layout.initHeight
            const width = layout.initWidth

            // Grab index of last screen
            const lastSceneIndex = scenes[scenes.length - 1].index

            // Calculates change in indices
            const deltaScene = (lastSceneIndex - toIndex == 0) ? 1 : (lastSceneIndex - toIndex);

            let translateX = position.interpolate({
                inputRange: [thisSceneIndex - 1, thisSceneIndex],
                // Adjusts how the output get scaled
                outputRange: [width / deltaScene, 0],
            });

            const translateY = position.interpolate({
                // Not used, but in the link they use this for vertical transitions
                // If you're interested in that this would do it
                inputRange: [0, thisSceneIndex],
                outputRange: [height, 0]
            });

            // MAGIC HAPPENS HERE:
            // Test whether we're skipping back more than one screen
            // and slide from bottom if true
            if (lastSceneIndex - toIndex > 1) {
                // If you want the screen to which you are transitioning to not move
                // have return; in this if block (see link). If you want behaviour as 
                // shown in the GIF then leave this
                if (scene.index === toIndex) {
                    return { transform: [{ translateX }] }
                }
                // BIG MAGIC HERE
                // Hide all screens in between
                if (scene.index !== lastSceneIndex) return { opacity: 0 }

            }
            return { transform: [{ translateX }] }
        },
    }),
}
);

So, for example, let's say your stack is something like [A, B, C, D] (so D is on top). lastSceneIndex grabs the index of the last scene (in this case D). DeltaScene calculates how much the scenes are changing. DeltaScene will only be != 1 when one goes back more than 1 screen. If you aren't familiar with how translateX works, I would strongly recommend reading the afore mentioned link; the reason why the output range is [width/deltaScene, 0] is before the animation starts, the screens are stacked on top of each other and then the animation scales will spread them out like [A][B][C][D] where each screen is width/deltaScene away. In this example, deltaScene is 3, so B is width / 3 to the right of A, C is 2 * width / 3 to the right of A, and D is 3 * width / 3 = width to the right of A, which is what we want. Without deltaScene, D would fly off to the right 3 times faster than A comes onto the screen, which makes for an ugly transition.

Also, I doubt anyone needs to see this but just in case, here's how it gets used in App.js (the provider is for redux, so that can probably be omitted if you don't want to deal with that)

<Provider store={store}>
  <ImageBackground source={require('./assets/background.png')} style={styles.backgroundImage} resizeMode='cover'>
    <AppNavigator />
  </ImageBackground>
</Provider>
...
const styles = StyleSheet.create({
  backgroundImage: {
    flex: 1,
  }
});

I really hope this helps! I'm still pretty new to react native and whatnot, so if I made any mistakes or there's any room for further improvement or explanation, please say so in the comments for me/anyone else that might see this!

0 users liked answer #6dislike answer #60
Dean Ogle profile pic
Dean Ogle

Copyright © 2022 QueryThreads

All content on Query Threads is licensed under the Creative Commons Attribution-ShareAlike 3.0 license (CC BY-SA 3.0).