I create a menu with React.js. But there is a problem for me. There is 2 sub menus and these sub menus have their menu items. When I hover one of them it shows its menu items. But when I click another sub menu, it still shows previous menu items. How can I prevent it ? My code:
My first class : Sider.js
function Sider(props) {
return (
<Menu mode="horizontal">
selectedKeys={[props.current]}
onClick={props.handleClick}
<SubMenu title={<span><Icon type="setting" />Sub menu 1</span>}>
<MenuItem> menu item 1</MenuItem>
<MenuItem> menu item 2</MenuItem>
</SubMenu>
<SubMenu title={<span><Icon type="laptop" />Sub menu 2</span>}>
<MenuItem> menu item 3</MenuItem>
</SubMenu>
</Menu>
);
}
This is my main class which I call Sider function.
Main.js :
import PropTypes from 'prop-types';
class Main extends React.Component {
constructor(props) {
super(props)
this.state = {
current: 'MenuItem'
}
}
handleClick = (e) => {
this.setState({
current: e.key,
});
}
render() {
return (
<div>
<Sider navigation={this.props.navigation} handleClick={this.props.handleClick} current={this.state.current />
</div>
);
}
}
Main.propTypes = {
handleClick: PropTypes.func,
};
You can use key to maintain active state of selected sub menu and use selectedKeys. see the working code below
I have made working menu in codeopen here codepen
import PropTypes from 'prop-types';
class App extends React.Component {
state = {
current: 'menu1:1',
}
handleClick = (e) => {
this.setState({
current: e.key,
});
}
render() {
return (
<div>
<Sider navigation={this.props.navigation} handleClick={this.handeClick} current={this.state.current} />
</div>
);
}
}
App.propTypes = {
handleClick: PropTypes.func,
};
And your Sider function will be like below
function Sider(props) {
return (
<Menu onClick={props.handleClick} selectedKeys={[props.current]}>
<SubMenu title={<span><Icon type="setting" />Sub menu 1</span>}>
<MenuItem key="setting:1"> menu item 1</MenuItem>
<MenuItem key="setting:2"> menu item 2</MenuItem>
</SubMenu>
<SubMenu title={<span><Icon type="laptop" />Sub menu 2</span>}>
<MenuItem key="laptop:1"> menu item 3</MenuItem>
</SubMenu>
</Menu>
);
}
For documentations please read horizontal menu and Vertical Menu
How did you implement the hovering mechanism? Please add more code.
Do you change the inner state of each SubMenu when you hover out?
You should better read the documentation for antd's components.
Go to the following link to better understand how to use these components.
Related
I have antd Menu inside collapsible Sider. I've set default open keys for one of Submenus and they should open one at a time. Here is my code for the Menu:
const MainMenu = ({ defaultOpenKeys }) => {
const [openKeys, setOpenKeys] = useState(defaultOpenKeys);
const rootKeys = ["sub1", "sub2"];
// Open only one submenu at a time
const onOpenChange = props => {
const latestOpenKey = props.find(key => openKeys.indexOf(key) === -1);
if (rootKeys.indexOf(latestOpenKey) === -1) {
setOpenKeys(props);
} else {
setOpenKeys(latestOpenKey ? [latestOpenKey] : defaultOpenKeys);
}
};
return (
<Menu
theme="dark"
openKeys={openKeys}
defaultSelectedKeys={["1"]}
mode="inline"
onOpenChange={onOpenChange}
>
<Menu.Item key="1">
Option 1
</Menu.Item>
<SubMenu key="sub1" title="User">
<Menu.Item key="2">Tom</Menu.Item>
</SubMenu>
<SubMenu key="sub2" title="Team">
<Menu.Item key="3">Team 1</Menu.Item>
</SubMenu>
</Menu>
);
};
export default MainMenu;
I pass defaultOpenKeys from the Sider.
const SiderDemo = () => {
const [collapsed, setCollapsed] = useState(false);
const toggleSider = () => {
setCollapsed(!collapsed);
};
return (
<Layout style={{ minHeight: "100vh" }}>
<Button type="primary" onClick={toggleSider}>
{React.createElement(
collapsed ? MenuFoldOutlined : MenuUnfoldOutlined
)}
</Button>
<Sider
collapsible
collapsed={collapsed}
collapsedWidth={0}
trigger={null}
>
<Menu defaultOpenKeys={["sub1"]} />
</Sider>
...
</Layout>
);
};
It works on mount, but when I collapse the Sider, defaultOpenKeys are being reset. How can I keep defaultOpenKeys from being reset, when the Sider is collapsed?
I have created a codesandbox and added console log in the Menu. You can see that defaultOpenKeys and openKeys are the same on mount. When I collapse the Sider, the console log is triggered twice. The first time defaultOpenKeys and openKeys are the same. And the second time openKeys become empty. How can I fix that?
Reason: on closing the sidebar it is closing opened sidemenu so it gonna trigger openchange with empty array and hence your logic making it reset to empty.
Here is code sandbox link with updated code
https://codesandbox.io/s/sider-demo-0der5?file=/Menu.jsx
Suggestion: Its anti pattern to assign props to initial state. if prop value changed in parent component then the new prop value will never be displayed because intial state will never update the current state of the component. The initialization of state from props only runs when the component is first created
I would like to add checkboxes inside Antd Submenu. Following is my code.
<Menu style = {{height: '100vh', overflow: 'auto'}} mode="inline" inlineCollapsed = "false">
<SubMenu key="sub1" onTitleClick = {subMenuTitleClick} title={<span><Icon type="mail" onClick = {this.temp}/><Checkbox onClick = {this.checkboxClick}></Checkbox><span>Sources</span></span>}>
<Menu.Item key={key1}>{detail.docTtl}</Menu.Item>
</SubMenu>
</Menu>
Here, click on Submenu should call subMenuTitleClick and click on checkbox should call checkboxClick.
Ok, so I don't have the full scope but I'll try some assumptions :
Do not use whitespeces in properties declarations :
onClick={} : good. onClick = {} : bad
onTitleClick={subMenuTitleClick} is not a click event, so make sure the <SubMenu/> component handles it correctly
Your <Checkbox/> has a onClick={this.temp}: make sure that this.temp is actually a click handler function
Here is an example. I removed a few things to make the whole more readable
class MyClass extends React.Component {
handleWhateverClic = () => { /* Do stuff */ }
handleCheckboxClick = () => { /* Do stuff */ }
render() {
return (
<Menu >
<SubMenu onTitleClick={this.handleWhateverClic} title={
<span>
<Icon type="mail" onClick={this.handleWhateverClic}/>
<Checkbox onClick={this.handleCheckboxClick} />
<span>Sources</span>
</span>
}>
<Menu.Item>{detail.docTtl}</Menu.Item>
</SubMenu>
</Menu>
)
}
}
I use antd 3.15 and GraphQL to fetch data and generate a list of SubMenu and Menu.Item inside of Menu. However, I got the error message like this Uncaught TypeError: Cannot read property 'isRootMenu' of undefined I have no idea what is wrong with my code. isRootMenu is not a prop listed anywhere on the doc. ant.design/components/menu/#header and when I hardcoded all the SubMenu and Menu.List there is no problem. Can I iterate data from GraphQL to generate the SubMenu and Menu.List?
Can someone help me with this issue, please? Thank you! Here is my code:
import * as React from 'react';
import './SideNav.scss';
import { Menu, Icon } from 'antd';
import gql from 'graphql-tag';
import { Query } from 'react-apollo';
const FLOORS_QUERY = gql`
query {
getAllFloors {
id
floorName
rooms {
id
roomName
roomNumber
roomDescription
}
}
}
`;
export default class SideNav extends React.Component {
render() {
return (
<Menu theme="light" defaultSelectedKeys={['1']} mode="inline">
<Query query={FLOORS_QUERY}>
{({ loading, error, data }) => {
if (loading) return <h4> loading... </h4>;
if (error) console.log(error);
console.log(data);
return (
<React.Fragment>
{data.getAllFloors.map((floor) => (
<SubMenu
key={floor.id}
title={
<span>
<Icon type="plus" />
<span>{floor.floorName}</span>
</span>
}
>
<React.Fragment>
{floor.rooms.map((room) => (
<Menu.Item key={room.id}>{room.roomNumber}</Menu.Item>
))}
</React.Fragment>
</SubMenu>
))}
</React.Fragment>
);
}}
</Query>
</Menu>
);
}
}
You should pass the props to the submenu.
const CustomComponent = (props) => (
<Menu.SubMenu title='SubMenu' {...props}>
<Menu.Item>SubMenuItem</Menu.Item>
</Menu.SubMenu>
)
so a solution to your question would be to do the following;
move the query outside of the containing menu
pass the props to the SubMenu
const FloorMapSubMenu = ({ id, floorName, rooms, ...other }) => {
return (
<Menu.SubMenu
key={id}
title={
<span>
<Icon type="plus" />
<span>{floorName}</span>
</span>
}
{...other} // notice the other props, this is were the 'isRootMenu' is injected from the <Menu> children
>
<React.Fragment>
{rooms.map((room) => (
<Menu.Item key={room.id}>{room.roomNumber}</Menu.Item>
))}
</React.Fragment>
</Menu.SubMenu>
)
}
class SideNav extends React.Component {
render() {
return (
<Query query={FLOORS_QUERY}>
{({ loading, error, data }) => {
if (loading) return <h4> loading... </h4>
if (error) console.log(error)
console.log(data)
return (
<Menu theme='light' defaultSelectedKeys={['1']} mode='inline'>
{data.getAllFloors.map((floor, i) => (
<FloorMapSubMenu key={i} id={floor.id} floorName={floor.floorName} rooms={floor.rooms} />
))}
</Menu>
)
}}
</Query>
)
}
}
I found out the Ant design SubMenu needs to use the parent to check some properties like isRootMenu at
SubMenu.js:260
getPopupContainer = props.parentMenu.isRootMenu ? props.parentMenu.props.getPopupContainer : function (triggerNode) {
return triggerNode.parentNode;
}
In order to solve it you need to manually pass parent props into SubMenu like
<Menu.SubMenu {...this.props}/>
to solve the problem. Hope this helps u
Related Github issue item https://github.com/react-component/menu/issues/255
I had this issue while trying to add a <div> as a child of Menu. I just added an empty Menu.Item as the first child of my menu, and the error went away.
<Menu>
<Menu.Item style={{display: 'none'}} />
<div>...</div>
</Menu>
I ran into the same issue. It seems Antd does not allow to place arbitrary components into a Menu/SubMenu. My guess is that Menu.Item needs to get some props, which are passed from Menu/SubMenu to its children.
So you can either create a custom component that passes all props down, or remove the inner <React.Fragment> declaration (the one that is inside the SubMenu), which is not needed anyway.
I was able to make it work by putting the <Query> Component at the top:
<Query query={FLOORS_QUERY}>
{({ loading, error, data }) => {
if (loading) return <Spin />;
if (error) console.log(error);
console.log(data);
return (
<Menu theme="light" defaultSelectedKeys={['1']} mode="inline">
{data.getAllFloors.map((floor) => (
<SubMenu
key={floor.id}
title={
<Link to="/{floor.id}">
<span>
<Icon type="plus" />
<span>{floor.floorName}</span>
</span>
</Link>
}
>
{floor.rooms.map((room) => (
<Menu.Item key={room.id} onClick={this.showRoomProfile}>
{room.roomNumber}
</Menu.Item>
))}
</SubMenu>
))}
</Menu>
);
}}
</Query>
According to the Typescript definitions the childrens of Menu should be of kind Item, SubMenu, ItemGroup or Divider. If you must place a different component on the Header, wrap the Menu and the desired component on a Header component component as such:
import { Layout } from 'antd';
const { Header, Footer, Sider, Content } = Layout;
<Layout>
<Layout.Header>
<div className="logo" />
<Menu>
<Menu.Item key="1">nav 1</Menu.Item>
<Menu.Item key="2">nav 2</Menu.Item>
</Menu>
<Layout.Header>
</Layout>
I have run into the same issue. But my issues was I have using ternary condition to show some menu's dynamically inside part used the <></> element. That caused me this issue. Once removed that everything work fine.
I have a Menu as bellow:
<Menu>
<SubMenu>
<Menu.Item>Help</Menu.Item>
<Menu.Item>Antd</Menu.Item>
</SubMenu>
</Menu>
Now the problem is that I need to hide SubMenu or Menu.Item for some situations? something like below :
<Menu>
<Acl item="submenu">
<SubMenu>
<Acl item="help">
<Menu.Item>Help</Menu.Item>
</Acl>
<Menu.Item>Antd</Menu.Item>
</SubMenu>
</Acl>
</Menu>
The Acl component will check for user access to the menu and decide to hide or show the text.
Is there any code examples for antd to reach this result or any suggestions?
Note: Already I have implemented Acl as bellow:
import React, { Component } from 'react';
class Acl extends Component {
constructor(props) {
super(props);
}
render() {
const props = this.props;
if( hasAccess(props.item) )
return <div>{props.children}</div>;
return null;
}
}
export default Acl;
But after render I got this error:
Cannot read property 'indexOf' of undefined
I think it could be done as bellow:
render() {
const { getAccessByPath } = this.props; //selector from redux store
<Menu>
{ getAccessByPath('submenu') ?
<SubMenu>
{ getAccessByPath('submenu.help') ?
<Menu.Item>Help</Menu.Item> : ''
}
<Menu.Item>Antd</Menu.Item>
</SubMenu> : ''
}
</Menu>
}
This works now but is so nasty and is not readable if I have more submenus an menu items (currently about 20) but I think there should be a better solution.
You can use the disabled prop of Menu.Item and Menu.Submenu and use it to disable the button according to user type:
<Menu.Item disabled={userAccess()}> // userAccess() should return a boolean
In case you want to totally hide the button, you need to use conditional rendering, of the sort:
<Menu>
<Acl item="submenu">
{ (userAccess()) ? // again should return boolean
<SubMenu>
<Acl item="help">
<Menu.Item>Help</Menu.Item>
</Acl>
<Menu.Item>Antd</Menu.Item>
</SubMenu> : ''
}
</Acl>
</Menu>
I have a material ui list in my one of the component. When I click any item of this list, I go to another listView. I am using router to go to another listView. And using onClick method. Whenever I click any list item of first list I print "firstList clicked". and whenever I click any item if second list, it print "secondList clicked".
Here is my Problem:
When I click the ListItem of first list, the console.log("secondList clicked") also gets printed with "firstList Clicked" automatically. I have four list items in second list, so my console print output looks like this
firstList Clicked
secondList Clicked
secondList Clicked
secondList Clicked
secondList Clicked
Why is this happening?
Here is my code.
SecondList code
class TagListItemDetails extends Component {
handleClick() {
console.log("secondList clicked")
}
handleButtonClick() {
browserHistory.push("TagList")
}
render() {
return (
<MuiThemeProvider>
<div>
<List id="parent-list-tags">
<ListItem primaryText="Kitchen" onTouchTap={this.handleClick()}/>
<ListItem primaryText="Beach" onClick={this.handleClick()}/>
<ListItem primaryText="Marriage" onClick={this.handleClick()}/>
<ListItem primaryText="Garden" onClick={this.handleClick()}/>
</List>
<div className="backButton">
<RaisedButton backgroundColor="#293C8E" label="Back" onClick={this.handleButtonClick} labelColor="white">
</RaisedButton>
</div>
</div>
</MuiThemeProvider>
);
}
}
const mapStateToProps =(state) =>{
return {
tags: state.tagReducer
};
};
function matchDispatchToProps(dispatch){
return bindActionCreators({tagSelected: tagSelected}, dispatch);
}
export default connect(mapStateToProps, matchDispatchToProps)(TagListItemDetails);
firstList
export default class TagList extends Component {
handleClicked() {
console.log("firstList Clicked")
browserHistory.push("TagListItemDetails")
}
render() {
return (
<div>
<List id="parent-list-tags" >
<ListItem primaryText="Scene" onClick={this.handleClicked} />
<Divider/>
<ListItem primaryText="Actors" onClick={this.handleClicked} />
<Divider/>
<ListItem primaryText="Emotions" onClick={this.handleClicked} />
<Divider/>
<ListItem primaryText="Actions" onClick={this.handleClicked}/>
<Divider/>
<ListItem primaryText="Objects" onClick={this.handleClicked}/>
<Divider/>
<ListItem primaryText="Compliance" onClick={this.handleClicked} />
</List>
<AddButton />
</div>
)
}
};
The problem is that in the SecondList, you are invoking the handleClick method as soon as the component is loaded. Try removing the parentheses () from the onClick handler. So instead of
<ListItem primaryText="Beach" onClick={this.handleClick()}/>
you can use:
<ListItem primaryText="Beach" onClick={this.handleClick}/>
------------------------------------------------------^^ No parentheses here
One way of passing arguments to a click handler passed as a prop is to use the fat arrow function:
onClick={() => this.props.itemSelected(2)}
// or if you want to pass the event as well:
onClick={(event) => this.props.itemSelected(2, event)}
Also, here is a demo of how to fire two functions on onClick event : http://codepen.io/PiotrBerebecki/pen/YGRQrG