I create a menu and want to highlight the item which i choose,and i did it. But when i press back/forward button,the menu item don\'t highlight. What should i do?
I ha
I could come up with a solution using WithRouter
import React,{ Component } from 'react';
import { NavLink, withRouter } from 'react-router-dom';
import { Layout, Menu, Icon } from 'antd';
import PropTypes from 'prop-types';
const { Sider } = Layout;
class SideMenu extends Component{
static propTypes = {
location: PropTypes.object.isRequired
}
render() {
const { location } = this.props;
return (
<Sider
trigger={null}
collapsible
collapsed={this.props.collapsed}>
<div className="logo" />
<Menu
theme="dark"
mode="inline"
defaultSelectedKeys={['/']}
selectedKeys={[location.pathname]}>
<Menu.Item key="/">
<NavLink to="/">
<Icon type="home" />
<span>Home</span>
</NavLink>
</Menu.Item>
<Menu.Item key="/other">
<NavLink to="/other">
<Icon type="mobile"/>
<span>Applications</span>
</NavLink>
</Menu.Item>
<Menu.Item key="/notifications">
<NavLink to="/notifications">
<Icon type="notification" />
<span>Notifications</span>
</NavLink>
</Menu.Item>
</Menu>
</Sider>
)
}
}
export default withRouter(SideMenu);
@Nadun's solution works for paths that don't contains arguments. If you're however using arguments in your routes, like me, here's a solution that should work for any route path, including /users/:id
or crazy stuff like /users/:id/whatever/:otherId
. It uses react-router's matchPath
API, which uses the exact same logic as the Router
component.
// file with routes
export const ROUTE_KEYS = {
ROOT: "/",
USER_DETAIL: "/users/:id",
};
export const ROUTES = {
ROOT: {
component: Home,
exact: true,
key: ROUTE_KEYS.ROOT,
path: ROUTE_KEYS.ROOT,
},
USER_DETAIL: {
component: Users,
key: ROUTE_KEYS.USER_DETAIL,
path: ROUTE_KEYS.USER_DETAIL,
},
};
.
// place within the App component
<Router>
<Layout>
<MyMenu />
<Layout>
<Layout.Content>
{Object.values(ROUTES).map((route) => (
<Route {...route} />
))}
</Layout.Content>
</Layout>
</Layout>
</Router>
.
// MyMenu component
const getMatchedKey = (location) =>
(
Object.values(ROUTES).find((route) =>
matchPath(location.pathname, route)
) || {}
).path;
const MyMenu = ({ location }) => {
return (
<Layout.Sider>
<AntMenu mode="inline" selectedKeys={[getMatchedKey(location)]}>
<AntMenu.SubMenu
title={
<React.Fragment>
<Icon type="appstore" />
Home
</React.Fragment>
}
>
<AntMenu.Item key={ROUTE_KEYS.ROOT}>
<Icon type="appstore" />
<span className="nav-text">
Some subitem
</span>
</AntMenu.Item>
</AntMenu.SubMenu>
<AntMenu.SubMenu
title={
<React.Fragment>
<Icon type="user" />
Users
</React.Fragment>
}
>
<AntMenu.Item key={ROUTE_KEYS.USER_DETAIL}>
<Icon type="user" />
<span className="nav-text">
User detail
</span>
</AntMenu.Item>
</AntMenu.SubMenu>
</AntMenu>
</Layout.Sider>
);
};
export default withRouter(MyMenu);
I do something like this but it doesn't seem to be reactive. Like if I navigate to a new page through a button (not from the menu items), it will not update the active link until the page refreshes.
import React from 'react';
import { StyleSheet, css } from 'aphrodite'
import { browserHistory, Link } from 'react-router';
import 'antd/lib/menu/style/css';
import 'antd/lib/icon/style/css';
import 'antd/lib/row/style/css';
import 'antd/lib/col/style/css';
import 'antd/lib/message/style/css';
import { appConfig } from '../../modules/config';
import { Menu, Icon, Row, Col, message } from 'antd';
const SubMenu = Menu.SubMenu;
const MenuItemGroup = Menu.ItemGroup;
const { appName } = appConfig;
const AppNavigation = React.createClass({
getInitialState() {
return {
current: this.props.pathname
};
},
handleClick(e) {
browserHistory.push(e.key);
this.setState({ current: e.key });
return;
},
render() {
return (
<Row className='landing-menu' type="flex" justify="space-around" align="middle" style={{height: 55, zIndex: 1000, paddingLeft: 95, color: '#fff', backgroundColor: '#da5347', borderBottom: '1px solid #e9e9e9'}}>
<Col span='19'>
<Link to='/'>
<h2 style={{fontSize: 21, color: '#fff'}}>
{appName}
<Icon type="rocket" color="#fff" style={{fontWeight: 200, fontSize: 26, marginLeft: 5 }}/>
</h2>
</Link>
</Col>
<Col span='5'>
<Menu onClick={this.handleClick} selectedKeys={[this.state.current]} mode="horizontal" style={{height: 54, backgroundColor: '#da5347', borderBottom: '0px solid transparent'}}>
<Menu.Item style={{height: 54, }} key="/">Home</Menu.Item>
<Menu.Item style={{height: 54, }} key="/signup">Signup</Menu.Item>
<Menu.Item style={{height: 54, }} key="/login">Login</Menu.Item>
</Menu>
</Col>
</Row>
);
},
});
export const App = React.createClass({
propTypes: {
children: React.PropTypes.element.isRequired,
},
componentWillMount(){
if (Meteor.userId()) {
browserHistory.push('/student/home')
}
},
render() {
return (
<div style={{position: 'relative'}}>
<AppNavigation pathname={this.props.location.pathname} />
<div style={{minHeight: '100vh'}}>
{ this.props.children }
</div>
</div>
);
}
});
EDIT:
the below works pretty well. pass down the pathname from react-router and pop that as a prop into selectedKeys
import React from 'react';
import { StyleSheet, css } from 'aphrodite'
import { browserHistory, Link } from 'react-router';
import 'antd/lib/menu/style/css';
import 'antd/lib/icon/style/css';
import 'antd/lib/row/style/css';
import 'antd/lib/col/style/css';
import 'antd/lib/message/style/css';
import { appConfig } from '../../modules/config';
import { Menu, Icon, Row, Col, message } from 'antd';
const SubMenu = Menu.SubMenu;
const MenuItemGroup = Menu.ItemGroup;
const { appName } = appConfig;
const AppNavigation = React.createClass({
getInitialState() {
return {
current: this.props.pathname
};
},
handleClick(e) {
browserHistory.push(e.key);
this.setState({ current: e.key });
return;
},
render() {
return (
<Row className='landing-menu' type="flex" justify="space-around" align="middle" style={{height: 55, zIndex: 1000, paddingLeft: 95, color: '#fff', backgroundColor: '#da5347', borderBottom: '1px solid #e9e9e9'}}>
<Col span='19'>
<Link to='/'>
<h2 style={{fontSize: 21, color: '#fff'}}>
{appName}
<Icon type="rocket" color="#fff" style={{fontWeight: 200, fontSize: 26, marginLeft: 5 }}/>
</h2>
</Link>
</Col>
<Col span='5'>
<Menu onClick={this.handleClick} selectedKeys={[this.props.pathname]} mode="horizontal" style={{height: 54, backgroundColor: '#da5347', borderBottom: '0px solid transparent'}}>
<Menu.Item style={{height: 54, }} key="/">Home</Menu.Item>
<Menu.Item style={{height: 54, }} key="/signup">Signup</Menu.Item>
<Menu.Item style={{height: 54, }} key="/login">Login</Menu.Item>
</Menu>
</Col>
</Row>
);
},
});
export const App = React.createClass({
propTypes: {
children: React.PropTypes.element.isRequired,
},
componentWillMount(){
if (Meteor.userId()) {
browserHistory.push('/student/home')
}
},
render() {
return (
<div style={{position: 'relative'}}>
<AppNavigation pathname={this.props.location.pathname} />
<div style={{minHeight: '100vh'}}>
{ this.props.children }
</div>
</div>
);
}
});
you can set the paths of the link as keys on each Menu.Item . then selectedKeys={this.props.location.pathname}
<Menu
theme="light"
mode='inline'
selectedKeys={[this.props.location.pathname,]}
>
<Menu.Item key={item.path} style={{float:'right'}}>
Link to={item.path}>{item.name}</Link>
</Menu.Item>
{menulist}
</Menu>
Item would be set active according to the current path. i added [] and trailing comma because selectedKeys accepts array while this.props.location.pathname is a String. i just code as hobby so idont know if its acceptable.
Intercepts the current URL and then set selectedKeys(Note that it is not defaultSelectedKeys).
componentWillMount(){
hashHistory.listen((event)=>{
pathname = event.pathname.split("/");
if(pathname != null){
this.setState({
test:pathname[1]
});
}
});
}
The following answer assumes you are using hooks. I know you are not in your question, but it might be useful for other people. In addition, this solution will work if you have nested paths such as /banner/this/is/nested
, and it works not only when pressing back and forward buttons but also when refreshing the current page:
import React, { useState, useEffect } from 'react'
import { useHistory, useLocation } from 'react-router-dom'
import { Layout, Menu } from 'antd'
const { Sider } = Layout
const items = [
{ key: '1', label: 'Invoices', path: '/admin/invoices' },
{ key: '2', label: 'Service Details', path: '/admin/service-details' },
{ key: '3', label: 'Service Contract Details', path: '/admin/service-contract-details' },
{ key: '4', label: 'Cost Centers', path: '/admin/cost-centers' },
{ key: '5', label: 'Clients', path: '/admin/clients' },
{ key: '6', label: 'Vendors', path: '/admin/vendors' }
]
const Sidebar = () => {
const location = useLocation()
const history = useHistory()
const [selectedKey, setSelectedKey] = useState(items.find(_item => location.pathname.startsWith(_item.path)).key)
const onClickMenu = (item) => {
const clicked = items.find(_item => _item.key === item.key)
history.push(clicked.path)
}
useEffect(() => {
setSelectedKey(items.find(_item => location.pathname.startsWith(_item.path)).key)
}, [location])
return (
<Sider style={{ backgroundColor: 'white' }}>
<h3 style={{ paddingLeft: '1rem', paddingTop: '1rem', fontSize: '1.25rem', fontWeight: 'bold', minHeight: 64, margin: 0 }}>
Costek
</h3>
<Menu selectedKeys={[selectedKey]} mode='inline' onClick={onClickMenu}>
{items.map((item) => (
<Menu.Item key={item.key}>{item.label}</Menu.Item>
))}
</Menu>
</Sider>
)
}
export default Sidebar
This is how the sidebar will look like: