instea/react-native-popup-menu

How to use with refs correctly?

Closed this issue ยท 5 comments

Hey there,

Your library is great and a joy to use (one of the only ones that work on windows nicely as well)! I'm already sorry for the wall of text, already tried to make it as short as possible :/.

I asked myself something while changing from my Implementation (<Menu name={MenuNames.DottedContextMenuName}) mentioned in #133 to one using refs instead and posted here as ppl searching for the same should find it via the two warnings quoted below.

I'm doing this because I ran into the problem of incorrect usage of popup menu - menu with name dottedContextMenu already exists (when navigating away from screenA) and menu with name dottedContextMenu does not exist when navigating back to ScreenA from ScreenB and clicking the icon in the header. (Which makes sense)

I'm using a CustomHeader which I pass to react-navigation options and the header has some icons (three dots for example).
The DottedContextMenuIcon component renders a DottedContextMenu which in fact is just the menu definition itself.

<Menu name={MenuNames.DottedContextMenu}>
    <MenuTrigger />
    ...rest of code

Okay, let's get to my question now :-).

I guess I can't use <Menu name anymore with refs as MenuProvider.openMenu(menuName) or MenuProvider.toggleMenu(menuName) expect strings and not a Ref Object?

So would the correct way be to have the following on the DottedContextMenu component

    onRef = (ref) => {
        this.menuRef = ref;
    };

    render() {
        return (
            <Menu ref={this.onRef}>
                <MenuTrigger />

and then in the DottedMenuIcon component (MenuService holds menuProviderRef)

    toggleMenu = () => {
        if (MenuService.isMenuOpen()) {
            this.dottedContextMenuRef!.menuRef!.close();
        } else {
            this.dottedContextMenuRef!.menuRef!.open();
        }
    };

    onRef = (ref: DottedContextMenu) => {
        this.dottedContextMenuRef = ref;
    };

    render() {
        return (
            <View style={[globalStyles.headerIconContainerDefaultStyle, this.props.containerStyle]}>
                <DottedContextMenu
                    ref={this.onRef}
                    onPressIcon={this.onSelectOption}
                    onSelectOption={this.onSelectOption}
                />

                <CircleButton
                    circleRadius={20}
                    roundContainerStyle={globalStyles.headerIconCircleButtonStyle}
                    androidRippleColor={Colors.androidRippleLight}
                    onPress={this.toggleMenu}
                    onLongPress={this.toggleMenu}
                >

Now, is this the "correct" way of using it with refs or am I doing something completely wrong and it could be done even simpler/more straigt forward (despite ref forwarding)?

Hi there.

You are right that name does not work with refs (it is just a string name) but you can use refs directly (as you already use in your example).

The problems with warnings are are because both screenA and B has the menu with same name (and navigation renders them as same time for the animations - just guessing).

Maybe just dummy question - what do you want to achieve? Just guessing that you want to open the menu by clicking on CircleButton both with onpress and onlongpress. Am I right?

I just made simple snack to see https://snack.expo.io/@sodik82/react-native-popup-menu-with-trigger-long-press

however it looks like menu would open onlongpress anyway (if there is no special long press handler)

@sodik82 Yes that was the case, it tried to render another DottedContextMenu when navigating to the child screen with the same name while the old one is still mounted (navigation.navigate from a Stack/TabNavStack to some child Stack).

About your question: Yeah that was about right. I basically just encapsulated the Menu into its own component for reusability in other places. The *MenuIcon components are solely to be rendered in the header, whereas the Menu itself (at least some of them) will be used in multiple places/screens.

Furthermore I don't really use the <MenuTrigger to show anything, that's done by the component that wants to integrate a "Menu". My *Menu component is solely responsible for the content of the Popover/Dialog/Whatever is rendered by it but not the "activator".

I basically just wanted to make sure the ref approach I switched to was the right way.

how to use with hooks in react-native?

The solution by @sodik82 works perfectly, just want to add that the functions triggered using refs won't work if <MenuTrigger> is not present. In that case, a warning would be thrown menu with name menu-3 does not exist, just render an empty <MenuTrigger to make it work.