react-router+antD/ How to highlight a menu item when press back/forward button?

北城以北 提交于 2021-01-20 23:54:53

问题


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 have tried to use addEventListener but failed.

Have someone could give some advice?

class Sidebar extends React.Component {
    constructor(props) {
        super(props);
        this.state={
            test: "home"
        }
        this.menuClickHandle = this.menuClickHandle.bind(this);
    }

    componentWillMount(){
        hashHistory.listen((event)=>{
            test1 = event.pathname.split("/");
        });
        this.setState({
            test:test1[1]
        });
    }

    menuClickHandle(item) {
        this.props.clickItem(item.key);
    }

    onCollapseChange() {
        this.props.toggle();
    }

    render() {
        var {collapse} = this.props;
        return (
            <aside className="ant-layout-sider">
                <Menu mode="inline" theme="dark" defaultSelectedKeys={[this.state.test || "home"]} onClick={this.menuClickHandle.bind(this)}>
                    <Menu.Item key="home">
                        <Link to="/home">
                            <Icon type="user"/><span className="nav-text">用户管理</span>
                        </Link>
                    </Menu.Item>
                    <Menu.Item key="banner">
                        <Link to="/banner">
                            <Icon type="setting"/><span className="nav-text">Banner管理</span>
                        </Link>
                    </Menu.Item>
                </Menu>
                <div className="ant-aside-action" onClick={this.onCollapseChange.bind(this)}>
                    {collapse ? <Icon type="right"/> : <Icon type="left"/>}
                </div>
            </aside>
        )
    }
}

回答1:


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);



回答2:


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]
                });
            }
        });
    }



回答3:


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.




回答4:


@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);



回答5:


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:




回答6:


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>
    );
  }


});


来源:https://stackoverflow.com/questions/41054657/react-routerantd-how-to-highlight-a-menu-item-when-press-back-forward-button

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!