Dynamic manipulation in React is straightforward with the mutation capabilities. First, I think you already did this, you need to import the JQuery libraries required
import $ from 'jquery';
import 'jquery-ui/ui/core';
import 'jquery-ui/ui/widgets/menu';
And you need to reference your menu in a react component as below. The dynamic lifecycle will be handled as a react state (https://facebook.github.io/react/docs/state-and-lifecycle.html). We use a state, for instance, menuItems, to manage the JSON object/arrays. You can see that the position is set to absolute. Without this, your menu will cause your entire document to expand, unless this is the behaviour you want.
<ul ref="menu" style={{display: "none", position: "absolute"}}>
{this.state.menuItems.map(this.processMenuEntry.bind(this, 0))}
</ul>
The implementation of the method processMenuEntry is below. Certainly you can update this method to match any different JSON structure.
I would recommend a small change to use className: "ui-state-disabled" instead of disabled: true. Hence, you will have more options to customise your styles rather than enablement/disablement only. The method below assumes nested levels, which start with zero, as in the initial call in the snippet above. And it is important to make sure to assign a key to a child component.
processMenuEntry(level, entry, idx){
let id= "menu"+ level+ "_"+ idx;
if(!entry.className){
entry.className= "";
}
let children= entry.children;
let child;
if(children && children.length){
child= <li className= {entry.className} key= {id}><div>{entry.label}</div><ul>{children.map(this.processMenuEntry.bind(this, level+1))}</ul></li>;
}else{
child= <li className= {entry.className} key= {id}><div>{entry.label}</div></li>;
}
return child;
}
Make sure in your constructor to make this call, so your method will have the same context of your react component in HTML
this.processMenuEntry= this.processMenuEntry.bind(this);
Add this code snippet in your componentDidMount, in order to start the menu API working, in addition to your click handler.
componentDidMount(){
$(this.refs.menu).menu({
select: function( event, ui ) {
//do
}
});
}
Now, for instance, if you want to disable the second item in your menu, you can simply do that.
simple[1].className= "ui-state-disabled";
this.setState({"menuItems": simple});
You can as well set an entirely different JSON array, and React will take care of it
this.setState({"menuItems": [{
label: "Some new Item1"
}]});
The use of "-" as a label will add a separator to your menu as the JQuery Menu API is designed.
Finally, in terms of handling the toggle of your menu, or hiding the menu when clicking outside it, you can look at the snippet below. Certainly, you can modify it based on your work. I am assuming below that we toggle the menu when clicking on an anchor. The check if the user clicked outside the menu is only added if the menu is visible.
<a href="https://stackoverflow.com" onClick={this.toggleMenu}>Test</a>
componentWillUnmount(){
this.stopMenuListeners();
}
stopMenuListeners(){
$(document).off("click.menuOutsideClicks");
}
toggleMenu(event){
let menu= $(this.refs.menu);
let visible= !menu.is(':visible');
menu.slideToggle("fast");
this.stopMenuListeners();
if(visible){
setTimeout(()=>{
this._handler= $(document).on("click.menuOutsideClicks", (event)=>{
if(!$(event.target).closest(menu).length){
menu.slideUp('slow').hide();
this.stopMenuListeners();
}
});
});
}
event.stopPropagation();
event.preventDefault();
}