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

后端 未结 6 2226
[愿得一人]
[愿得一人] 2021-02-12 17:54

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

相关标签:
6条回答
  • 2021-02-12 17:59

    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);
    
    0 讨论(0)
  • 2021-02-12 18:00

    @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);
    
    0 讨论(0)
  • 2021-02-12 18:04

    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>
        );
      }
    
    
    });
    
    0 讨论(0)
  • 2021-02-12 18:07

    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.

    0 讨论(0)
  • 2021-02-12 18:12

    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]
                    });
                }
            });
        }
    
    0 讨论(0)
  • 2021-02-12 18:22

    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:

    0 讨论(0)
提交回复
热议问题