react-native-navigationwix-react-native-navigationreact-native-navigation-v2

How to replace nested stack with other component in react-native-navigation v2?


I'm trying to build a dynamic wizard with react-native-navigation v2.
I say dynamic because the number of steps may vary depending on which options the user selects.

I was thinking to use nested stacks, so the layout of my app would look something like this:

{
  root: {
    sideMenu: {
      left: {/*...*/},
      center: {
        stack: {
          children: [component1, /*...,*/ componentX]
        }
      }
    }
  }
}

ComponentX is where I start my wizard, so I push a new stack like this:

{
// ...
  stack: {
    children: [
      component1,
      //...,
      componentX,
      {
        stack: {
          children: [step1, step2, /*...,*/ stepN]
        }
      }
    ]
  }
}

After the user make the last choice on stepN, I would like to replace the nested stack with a summary screen to have something like:

{
//...
  stack: {
    children: [
      component1,
      //...,
      componentX,
      summaryScreen
    ]
  }
}

I could use Navigation.setRoot to reset the whole thing but that means I would probably have to store the navigation in Redux. I've also tried using Navigation.setStackRoot but I'm under the impression that it's setting the parent stack root and not my nested stack...


Solution

  • I've finally managed to solve it.
    Here's how:

    1. At the start of my app, when I give an id to my main stack
    const sideMenu = {
        left: { /*...*/ },
        center: {
          stack: {
            id: 'main', // this line is important
            children: [/*...*/]
          }
        },
      };
      Navigation.setRoot({
        root: { sideMenu },
      });
    
    1. When I want to start my wizard, I push a new stack
    Navigation.push(componentId, {
      stack: {
        id: 'wizard',
        children: [
          {
            component: { /*...*/ },
          },
        ],
      }
    })
    
    1. I push the screens on the new stack wizard as the user progresses

    2. When I want to display the final summary screen, I call setStackRoot on the nested stack

    Navigation.setStackRoot('wizard', [
      {
        component: { /*...*/ },
      },
    ]);
    
    1. On that summary screen, I have a button labelled 'Finish' which removes the nested stack
    Navigation.pop('main');
    

    EDIT: with this approach alone, if you click on the back arrow when you are on a nested screen, it will dismiss the entire nested stack instead of this screen only.
    I had to use a custom back button as follows:

    I solved it by using a custom back button: 1. When pushing a new screen where I want to override the button, use the options

    import Icon from 'react-native-vector-icons/MaterialIcons';
    /* ... */
    const backIcon = await Icon.getImageSource('arrow-back', 24, '#000');
    const component = {
      id: screenID,
      name: screenID,
      passProps,
      options: {
        topBar: {
          leftButtons: [
            {
              id: 'backButton',
              icon: backIcon,
            },
          ],
        },
      }
    };
    return Navigation.push(componentId, { component });
    
    1. Create a HOC to implement you custom back action
    import React, { Component } from 'react';
    import { Navigation } from 'react-native-navigation';
    
    const getDisplayName = WrappedComponent => WrappedComponent.displayName || WrappedComponent.name || 'Component';
    
    export default function withCustomBackButton(WrappedComponent) {
      class WithCustomBackButton extends Component {
        componentDidMount() {
          this.navigationEventListener = Navigation.events().bindComponent(this);
        }
    
        componentWillUnmount() {
          if (this.navigationEventListener) this.navigationEventListener.remove();
        }
    
        navigationButtonPressed() {
          // Your custom action
          const { componentId } = this.props;
          Navigation.pop(componentId);
        }
    
        render() {
          return <WrappedComponent {...this.props} />;
        }
      }
    
      WithCustomBackButton.displayName = `WithCustomBackButton(${getDisplayName(WrappedComponent)})`;
    
      return WithCustomBackButton;
    }
    
    1. When registering the screen with the custom back button, wrap it in your HOC
    import withCustomBackButton from '../components/hoc/WithCustomBackButton';
    /* ... */
    Navigation.registerComponent('selectLocation', () => withCustomBackButton(SelectLocation));