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...
I've finally managed to solve it.
Here's how:
const sideMenu = {
left: { /*...*/ },
center: {
stack: {
id: 'main', // this line is important
children: [/*...*/]
}
},
};
Navigation.setRoot({
root: { sideMenu },
});
Navigation.push(componentId, {
stack: {
id: 'wizard',
children: [
{
component: { /*...*/ },
},
],
}
})
I push the screens on the new stack wizard
as the user progresses
When I want to display the final summary screen, I call setStackRoot on the nested stack
Navigation.setStackRoot('wizard', [
{
component: { /*...*/ },
},
]);
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 });
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;
}
import withCustomBackButton from '../components/hoc/WithCustomBackButton';
/* ... */
Navigation.registerComponent('selectLocation', () => withCustomBackButton(SelectLocation));