Sharath
Sharath

Reputation: 2428

Better solution to open the Menu when 3 dots are clicked in React Native

I am able to open menu when 3 dots icon is clicked for each item. But can the code be written in a better way..

Right now menu is getting created for each card item but ideally it would have been good to create single Menu View and dynamically associate it to some card where ever the 3 dots is clicked.

Expo Source Code Link

Code

export default class App extends React.Component {

  constructor(props, ctx) {
    super(props, ctx);
    this.state = {
      list: [
        { name: "Michael", mobile: "9292929292", ref: React.createRef() },
        { name: "Mason Laon Roah", mobile: "1232313233", ref: React.createRef() },
        { name: "Constructor", mobile: "4949494949", ref: React.createRef() },
        { name: "Rosling", mobile: "4874124584", ref: React.createRef() }
      ],
    };
  }

  _menu = null;

  hideMenu = () => {
    this._menu.hide();
  };

  showMenu = (ref) => {
    this._menu = ref;
    this._menu.show();
  };

  render() {
    const renderItem = ({ item, index }) => (
      <ListItem
          title={
            <View>
              <Text style={{ fontWeight: "bold" }}>{item.name}</Text>
              <Text>{item.mobile}</Text>
            </View>
          }
          subtitle={
            <View>
              <Text>445 Mount Eden Road, Mount Eden, Auckland. </Text>
              <Text>Contact No: 134695584</Text>
            </View>
          }
          leftAvatar={{ title: 'MD' }}
          rightContentContainerStyle={{ alignSelf: 'flex-start'}}
          rightTitle={this.getMenuView(item.ref)}
        />
    );

    return (
      <View style={styles.container}>
        <View style={{ flex: 1, marginTop: 30 }}>
          <FlatList
            showsVerticalScrollIndicator={false}
            keyExtractor={(item, index) => index.toString()}
            data={this.state.list || null}
            renderItem={renderItem}
            ItemSeparatorComponent={() => (
              <View style={{ marginBottom: 5 }} />
            )}
          />
        </View>
      </View>     
    );
  }

  getMenuView(ref) {
    return (
      <Menu
          ref={ref}
          button={<Icon onPress={() => this.showMenu(ref.current)} type="material" color="red" name="more-vert" />}
        >
          <MenuItem onPress={this.hideMenu}>Menu item 1</MenuItem>
          <MenuItem onPress={this.hideMenu}>Menu item 2</MenuItem>
          <MenuItem onPress={this.hideMenu} disabled>
            Menu item 3
          </MenuItem>
          <MenuDivider />
          <MenuItem onPress={this.hideMenu}>Menu item 4</MenuItem>
      </Menu>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
});

Sample Output

Upvotes: 3

Views: 16789

Answers (3)

Patricia Green
Patricia Green

Reputation: 524

There is now a React Native plugin for this. I'm not sure it was around when the question was originally asked. But I'm leaving this here for anyone else looking for the answer.

https://www.npmjs.com/package/react-native-popup-menu

The example worked for me. I wanted to use the vertical ellipsis, so I did this modification to the MenuTrigger part of the example to an icon instead of text:

        <MenuTrigger>
            <Icon name="more-vert" size={25} color={colors.rustRed} />
        </MenuTrigger>

As a side note, I had difficulty finding and using the ellipsis. I eventually went with using react-native-vector-icons by using 'npm -i react-native-vector-icons' and importing the Material Icons like this:

import Icon from 'react-native-vector-icons/MaterialIcons';

Upvotes: 1

Pavindu
Pavindu

Reputation: 3112

As mentioned here, you can find an undocumented UIManager.java class that allows you to create Popups with its showPopupMenu method.

This currently works only for Android.

import React, { Component } from 'react'
import { View, UIManager, findNodeHandle, TouchableOpacity } from 'react-native'
import Icon from 'react-native-vector-icons/MaterialIcons'

const ICON_SIZE = 24

export default class PopupMenu extends Component {
  constructor (props) {
    super(props)
    this.state = {
      icon: null
    }
  }

  onError () {
    console.log('Popup Error')
  }

  onPress = () => {
    if (this.state.icon) {
      UIManager.showPopupMenu(
        findNodeHandle(this.state.icon),
        this.props.actions,
        this.onError,
        this.props.onPress
      )
    }
  }

  render () {
    return (
      <View>
        <TouchableOpacity onPress={this.onPress}>
          <Icon
            name='more-vert'
            size={ICON_SIZE}
            color={'grey'}
            ref={this.onRef} />
        </TouchableOpacity>
      </View>
    )
  }

  onRef = icon => {
    if (!this.state.icon) {
      this.setState({icon})
    }
  }
}

Then use it as follows.

render () {
    return (
      <View>
        <PopupMenu actions={['Edit', 'Remove']} onPress={this.onPopupEvent} />
      </View>
    )
  }

onPopupEvent = (eventName, index) => {
    if (eventName !== 'itemSelected') return
    if (index === 0) this.onEdit()
    else this.onRemove()
}

Source: https://cmichel.io/how-to-create-a-more-popup-menu-in-react-native

Upvotes: 1

bhantol
bhantol

Reputation: 9616

Use React Portals

https://reactjs.org/docs/portals.html

In short the receipts is:

You define your dynamic menu at sibling level only once in the parent i.e. in your case it would be adjacent to App.

Handle Click at each item level to open your component. You can pass some specific event days to achieve the dynamism.

Easier example https://codeburst.io/reacts-portals-in-3-minutes-9b2efb74e9a9

This achieves exactly what you are trying to do which is defer the creation of component untill clicked.

Upvotes: 0

Related Questions