1

I would like to dynamically fill the 'menuItems' property of a MenuItem (https://www.material-ui.com/#/components/menu) from an array of values. I found several posts about using the map syntax, and indeed I managed to make it work to fill the Menu with MenuItem elements. However I didn't manage to make it work to fill the menuItems array (nested menus).

Any help appreciated, I'm new with react and javascript so it is likely I'm missing something obvious. Thanks

Here is what I wrote:

class PopupMenu extends React.Component {
  constructor(props) {
    super(props);
    this.state = { menu: props.menu, key: (props.menu.id + "_menu"), open: false, actions: null};
  }

  
...
  
  async createActionOrMenu(actionOrMenu) {
	  if (actionOrMenu[1] == true) {
		  // submenu
		  let menu = actionOrMenu[0];
		  let actionsOrMenus = await window.epic.content(menu);
		  return <MenuItem 
			  primaryText={label(menu.title)} 
			  menuItems={actionsOrMenus.map(this.createActionOrMenu.bind(this))} // the line I have problem with
		  />
	  } else {
		  //action
		  let action = actionOrMenu[0];
		  return <MenuAction action={action}/>
	  }
  }
  
  createActions(actionsOrMenus) {
	  if (!actionsOrMenus || actionsOrMenus.length === 0) {
		  return;
	  }
	  return actionsOrMenus.map(this.createActionOrMenu.bind(this)); // the map syntax to dynamically fill elements: works like charm
  }

  render() {
    return (
      <div>
        <FlatButton
		  className="menubar_menu"
          onClick={this.handleClick}
          label={label(this.state.menu.title)}
		  hoverColor="lightgrey"
		  primary = {this.state.open}
        />
        <Popover
          open={this.state.open}
          anchorEl={this.state.anchorEl}
          anchorOrigin={{horizontal: 'left', vertical: 'bottom'}}
          targetOrigin={{horizontal: 'left', vertical: 'top'}}
          onRequestClose={this.handleRequestClose}
        >				  
          <Menu key= {this.state.key+"popup"}>
		        {this.createActions(this.state.actions)}
          </Menu>
        </Popover>
      </div>
    );
  }
}

2
  • Here is the console output: Warning: Failed prop type: Invalid prop menuItems supplied to MenuItem, expected a ReactNode. in MenuItem Commented Apr 10, 2018 at 14:27
  • It seems that my problem is the async keyword for createActionOrMenu. The function returns a Promise instead of the jsx array Commented Apr 10, 2018 at 15:24

1 Answer 1

2

Here is a working code: The first issue I had to fix was my 'async' keyword, see comments. Then this line works: menuItems={createActions(this.state.actions)}

class MenuAction extends React.Component {
	constructor(props) {
		super(props);
		this.state = {action: props.action};
	}
	
	render () {
		return (
			<MenuItem primaryText={label(this.state.action.text)}/>
		);
	}
}

function createActionOrMenu(actionOrMenu) {
  if (!actionOrMenu || !actionOrMenu[0]) {
		console.error("invalid action/menu sent to createActionOrMenu");
		return;
	}
  if (actionOrMenu[1] == true) {
	  // submenu
	  return <SubMenu menu= {actionOrMenu[0]} key = {actionOrMenu[0].id + "_submenu"} />
  } else {
	  // action
	  return <MenuAction action= {actionOrMenu[0]} key = {actionOrMenu[0].id + "_action"}/>
  }
}

function createActions(actionsOrMenus) {
	if (!actionsOrMenus || actionsOrMenus.length === 0) {
		return;
	}
	return actionsOrMenus.map(createActionOrMenu);
}
  
  

class SubMenu extends React.Component {
	constructor(props) {
		super(props);
		this.state = { menu: props.menu, actions: []};
	}
	
	UNSAFE_componentWillMount() {
	  this.init();
	}
  
	async init() {
		let content = await window.epic.currentPage.menuBarSite.content(this.state.menu);
		this.setState({actions: content});  
	}	
	  
	 render() {
		 return <MenuItem 
			primaryText={label(this.state.menu.title)} 
			key={this.state.menu.title}
			menuItems={createActions(this.state.actions)}  // this works
		  />
	 }
}

class PopupMenu extends React.Component {
  constructor(props) {
    super(props);
    this.state = {menu: props.menu, actions: [], open: false};
  }

  UNSAFE_componentWillMount() {
	  this.init();
  }
  
  async init() {
	  let content = await window.epic.currentPage.menuBarSite.content(this.state.menu);
	  this.setState({actions: content});  
  }
  
  handleClick = (event) => {
    // This prevents ghost click.
    event.preventDefault();

    this.setState({
      open: true,
      anchorEl: event.currentTarget,
    });
  };

  handleRequestClose = () => {
    this.setState({
      open: false,
    });
  };
  

  render() {
    return (
      <div>
        <FlatButton
		  className="menubar_menu"
          onClick={this.handleClick}
          label={label(this.state.menu.title)}
		  hoverColor="lightgrey"
		  primary = {this.state.open}
        />
        <Popover
          open={this.state.open}
          anchorEl={this.state.anchorEl}
          anchorOrigin={{horizontal: 'left', vertical: 'bottom'}}
          targetOrigin={{horizontal: 'left', vertical: 'top'}}
          onRequestClose={this.handleRequestClose}
        >				  
          <Menu key= {this.state.key+"_popup"}>
		    {createActions(this.state.actions)}
          </Menu>
        </Popover>
      </div>
    );
  }
}

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.