React-native-navigation: [V2] Custom components for TopBar buttons don't send events

Created on 29 Jul 2018  ·  8Comments  ·  Source: wix/react-native-navigation

Issue Description

When using a custom component for a topBar button, press events are not sent to subscribers.

Steps to Reproduce / Code Snippets / Screenshots

  1. On any screen, show a modal with a custom component for the left button
  2. Within the modal component, subscribe to events
  3. Add console.log to navigationButtonPressed function in modal component
  4. Pop the modal; tap the left button
  5. Note that the callback is not fired.

Example:
Pushing modal with custom left top button:

Navigation.showModal({
    stack: {
        children: [{
            component: {
                name: 'navigation.main.AddToListModal',
                options: {
                    topBar: {
                        leftButtons: {
                            id: 'backButton',
                            component: {
                                name: 'navigation.topBar.ChevronLeft',
                                passProps: {
                                    color: 'white'
                                }
                            }
                        },
                        visible: true
                    }
                }
            }
        }]
    }
})

The modal:

class AddToListModal extends React.PureComponent<Props> {
    constructor(props: Props) {
        super(props);
        Navigation.events().bindComponent(this)
    }

    navigationButtonPressed({buttonId}) {
        if (buttonId == 'backButton') {
            Navigation.dismissModal(this.props.componentId);
        }
    }

    render() {
        return (
            <View>
                <Text>Hey</Text>
            </View>
        );
    }
}

The custom top bar component

class IconWrapper extends React.PureComponent {
    render() {
        return <Icon name='rowing' color={this.props.color || 'black'}/>
    }
}

Environment

  • React Native Navigation version: 2.0.2425
  • React Native version: 0.56.0
  • Platform(s) (iOS, Android, or both?): iOS
  • Device info (Simulator/Device? OS version? Debug/Release?): Device; iOS 11.4.1; Debug
🏚 stale

Most helpful comment

@Cicko but your solution looks like a v1 solution. Correct?

All 8 comments

yea it won't listen to custom component, you need to

leftButtons: {
     id: 'backButton',
     component: {
         name: 'navigation.topBar.ChevronLeft',
          passProps: {
              color: 'white',
             // pass event here
             onClick: () => this.onLeftButtonClick()
          }
     }
},

inside your component just call this function on press

I see. I think this is still somewhat problematic since I'm unable to reference the component backing the modal (I think this in your example would bind to the component _launching_ the modal).

In other words, the left button on the topBar in the modal I'm launching needs to either have a reference to the modal's componentId or the modal's component itself so that the left button can dismiss the modal. In the example you provided, is there a way to get a reference to the newly popped modal so that the custom component can dismiss it?

@chrisbenincasa yea, normally when you want to call that function inside your button component,
you can define a function inside the button class which will save the current component but you need a reference to the button, such that

inside the button class

componentWillMount() {
 this.props.passRef(this)
}

setCurrentComponentId(id) {
   this.setState({
      currentComponentId: id
   })
}

inside the parent view controller

....
_leftButtonRef
....
leftButtons: {
     id: 'backButton',
     component: {
         name: 'navigation.topBar.ChevronLeft',
          passProps: {
              color: 'white',
             // pass event here
             passRef: (ref) => this._leftButtonRef = ref
          }
     }
}

To get this to work I had to do something slightly more complex based on your example. The parent screen had to act as the communicator between the modal and the top button, since neither the parent view nor the top button is able to know about the modal's assigned componentId.

It ended up looking roughly like this:

var modalComponentId: string;
Navigation.showModal({
    stack: {
        children: [{
            component: {
                name: 'navigation.main.AddToListModel',
                passProps: {
                    setRef: (cid: any) => modalComponentId = cid
                },
                options: {  
                    topBar: {
                        leftButtons: [{
                            id: 'backButton',
                             component: {
                                 name: 'navigation.topBar.ChevronLeft',
                                 passProps: {
                                     text: 'Back',
                                     getModalRef: () => modalComponentId
                                 }
                            }
                        }],
                        rightButtons: [{
                            id: 'doneButton',
                            text: 'Done',
                            enabled: false
                        }],
                        visible: true
                    }
                }
            }
        }]
    }
})

And then in modal component:

class AddToListModal extends React.PureComponent<Props> {
    componentWillMount() {
        this.props.setRef(this.props.componentId)
    }

    render() {
        return (
            <View></View>
        );
    }
}

And lastly in the button component:

export default class ModalHeaderButton extends React.PureComponent {
    handlePress() {
        Navigation.dismissModal(this.props.getModalRef());
    }

    render() {
        return (
            <View></View>
        );
    }
}

Of course, this isn't really ideal, but it works. Hopefully there will be a cleaner way to achieve this at some point!

I fixed it by listening to any event in the navigator using:

this.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
````

and then you just check for:
```javascript
if (event.type === 'NavBarButtonPress')

Here you have a bigger part of the code:

class NavEventsManager {


    /**
     * This method configure the Navigation Events Manager so it will start capture events.
     * @param navigator
     */
    init(navigator) {
        this.navigator = navigator;
        this.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
    }

    /**
     * Navigation event handler.
     * @param event
     */
    onNavigatorEvent = (event) => {
        switch (event.type) {
            case 'DeepLink':
                this.manageDeepLinks(event);
                break;
            case 'ScreenChangedEvent':
                this.manageScreenChanged(event);
                break;
            case 'NavBarButtonPress':
                this.manageNavBarButtonPress(event);
                break;
            default:
                break;
        }
    };

    manageNavBarButtonPress = (event) => {
        this.Log.info('button press with ID:'.concat(event.id));
    };
    .
    . 
    .
}

Also I fixed with this the screen redirection through the navigation drawer using Deep Links.

@Cicko but your solution looks like a v1 solution. Correct?

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
If you believe the issue is still relevant, please test on the latest version and report back. Thank you for your contributions.

The issue has been closed for inactivity.

Was this page helpful?
0 / 5 - 0 ratings