alpertuna / react-metismenu

A ready / simple to use, highly customizable, updateable, ajax supported, animated and material designed menu component for React
https://www.npmjs.com/package/react-metismenu
MIT License
175 stars 95 forks source link

"Active" not being set and sub menu collapsing #35

Open NatalJR opened 6 years ago

NatalJR commented 6 years ago

Hi, whenever i click an item on the menu it takes me to the page it's supposed to go, but it doesn't set the "active" link and if the item is in a sub-menu, the sub-menu is closed.

At first i thought it was a problem with the store, but it seems to be working just fine.

Then i thought it was the same issue as #26, but activeLinkFromLocation is set.

When an item is selected it behaves like this (first image is when i open the navbar): menu

I'm not running on server side. And since it is quite possible it's an error on my code, here is what i think is relevant.

index.js

//React
import React from 'react';
import ReactDOM from 'react-dom';
//Components
import App from './App';
import EBox, {EGrid} from './components/Event';
import Death from './components/Death';
import Home from './components/Home';
import Login from './components/Login';
import Management from './components/ManagementReports'
import Statistic from './components/StatisticReports'
//Router
import {browserHistory, IndexRoute, Route, Router} from 'react-router';
//React Toolbox Themer (PostCSS Issues)
import theme from './toolbox/theme';
import ThemeProvider from 'react-toolbox/lib/ThemeProvider';
//Redux
import {applyMiddleware, combineReducers, createStore} from 'redux';
import {Provider} from 'react-redux';
import thunkMiddleware from 'redux-thunk';
//reducers
import {menus} from './reducers/menus';
import {death} from './reducers/death';
import {auth} from './reducers/auth';
import {reports} from './reducers/reports';
import metisMenuReducer from 'react-metismenu/lib/reducers';
//CSS
import './css/wizard.css';
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap/dist/css/bootstrap-theme.css';
import './css/font-awesome-4.7.0/css/font-awesome.min.css';
import './css/index.css';
import './css/App.css';
import './toolbox/theme.css';
import 'react-table/react-table.css';

// Create Reducers
const reducers = combineReducers({death, menus, auth, reports, metisMenuReducer});
const store = createStore(reducers, applyMiddleware(thunkMiddleware));

ReactDOM.render(
    (
        <ThemeProvider theme={theme}>
            <Provider store={store}>
                <Router history={browserHistory}>
                    <Route path="/login" component={Login}/>
                    <Route path="/" component={App}>
                        <IndexRoute component={Home}/>
                        <Route path="obitos" component={Death}/>
                        <Route path="abertas" component={EGrid}/>
                        <Route path="fechadas" component={EGrid}/>
                        <Route path="criar" component={EBox}/>
                        <Route path="gerenciais" component={Management}/>
                        <Route path="estatisticos" component={Statistic}/>
                    </Route>
                </Router>
            </Provider>
        </ThemeProvider>
    ),
    document.getElementById('root')
);

mainMenu css

.mainMenu {
    position: fixed;
    width: 100%;
    font-size: 12px;
    bottom: 0;
    left: 0;
    top: 0;
}

App.js

import React, {Component} from "react";
import NavegationApi from './logics/NavigationApi'
import NavDrawer from "react-toolbox/lib/layout/NavDrawer";
import RouterLink from "react-metismenu-router-link";
import Navigation from "react-toolbox/lib/navigation/Navigation";
import Link from "react-toolbox/lib/link/Link";
import AppBar from "react-toolbox/lib/app_bar/AppBar";
import Layout from "react-toolbox/lib/layout/Layout";
import Panel from "react-toolbox/lib/layout/Panel";
import FontIcon from "react-toolbox/lib/font_icon";
import Sidebar from 'react-toolbox/lib/layout/Sidebar';
import IconButton from 'react-toolbox/lib/button/IconButton';
import {connect} from 'react-redux';
import MetisMenu from "react-metismenu";

class App extends Component {

    render() {

        const menu = [
            {
                icon: 'dashboard',
                label: 'Sigtrans',
                to: '/'
            },
            {
                icon: 'bell',
                label: 'Ocorrencias',
                content: [
                    {
                        label: 'Abertas',
                        to: 'abertas'
                    },
                    {
                        label: 'Fechadas',
                        to: 'fechadas'
                    },
                    {
                        label: 'Criar',
                        to: 'criar',
                    }
                ]
            },
            {
                icon: 'ambulance',
                label: 'Ocorrencias fatais',
                to: 'obitos'
            },
            {
                icon: 'area-chart',
                label: 'Relatórios',
                content: [
                    {
                        label: 'Estatisticos',
                        to: 'estatisticos'
                    },
                    {
                        label: 'Gerenciais',
                        to: 'gerenciais'
                    },
                ]
            }
        ];
        return (
            <div id="root">
                <div className="main">
                    <Layout>
                        <NavDrawer pinned={this.props.menus.drawer} permanentAt='xxxl'>
                            <MetisMenu content={menu} LinkComponent={RouterLink}
                                       className='mainMenu'
                                       reduxStoreName={"metisMenuReducer"}
                                       useExternalReduxStore={this.context.store}
                                       activeLinkFromLocation
                            />
                        </NavDrawer>
                        <Panel>

                            <AppBar className="app-bar" title=" " flat
                                    leftIcon={<FontIcon className="md-24 md-light" value='menu'/>}
                                    rightIcon={<FontIcon className="md-24 md-light" value='account_circle'/>}
                                    onLeftIconClick={this.props.toggleDrawer}
                                    onRightIconClick={this.props.toggleSidebar}
                            >
                                <Navigation type="horizontal">
                                    <Link href="#" className='app-bar' label="Mensagens" icon="inbox"/>
                                    <Link href="#" className='app-bar' active label="Perfil" icon="person"/>
                                </Navigation>
                            </AppBar>
                            <div style={{flex: 1, overflowY: 'auto', padding: '1.8rem'}}>
                                <div className="content-interior">
                                    {this.props.children}
                                </div>
                            </div>
                        </Panel>
                        <Sidebar pinned={this.props.menus.sidebar} width={5}>
                            <div><IconButton icon='close' onClick={this.props.toggleSidebar}/></div>
                            <div style={{flex: 1}}>
                                <p>Supplemental content goes here.</p>
                            </div>
                        </Sidebar>
                    </Layout>
                </div>
            </div>
        );
    }

}

App.contextTypes = {
    store: React.PropTypes.object.isRequired
};

const mapStateToProps = (state) => {
    return {
        menus: state.menus,
    }
};

const mapDispatchToProps = dispatch => {
    return {
        toggleDrawer: () => {
            dispatch(NavegationApi.toggleDrawer());
        },
        toggleSidebar: () => {
            dispatch(NavegationApi.toggleSidebar());
        }
    }
};

const AppContainer = connect(mapStateToProps, mapDispatchToProps)(App);

export default AppContainer;
alpertuna commented 6 years ago

Hi @NatalJR, I got it and published new patch v1.4.0-alpha.2. Now it supposed to be fixed.

NatalJR commented 6 years ago

Hi @alpertuna, thank you for the update. I've updated the component to alpha.2, and well, the issue persists. But when click a item on the menu that leads to a link that isn't routed, the menu behaves just fine. Im not sure why, but I suspect it's not exactly a problem with metismenu component, but it may be a problem in the interaction with react-router/browserHistory. Either that, or i really messed up somewhere.

alpertuna commented 6 years ago

Could you try adding / to each to property like this;

{
  label: 'Abertas',
  to: '/abertas'
}

Also to path properties of <Route>s

NatalJR commented 6 years ago

Sorry for taking so long to reply.

Tried that, still don't work.

I forgot to mention, when i select an item then close and open the menu drawer the sub menu is open and the item is correctly selected.

silentbobbert commented 6 years ago

I have seen similar issues with alpha2. Even calling this.refs.menu.changeActiveLinkTo('/users'); or using <MetisMenu activeLinkTo={this.state.activeLinkTo} /> does not set the active class on the menu items.

With hot reloading though, if i add/remove/add <MetisMenu activeLinkFromLocation /> The correct menu option will be active. Is this an order of operations, race or timing issue?

I have two instances of the metis menu (unique refs) in my component for different menus.

shobhitnpm commented 6 years ago

Same Issue I am facing sub menu collapsed after click sub menu item and on click item is not active, but on second click it becomes active.

version using "react-metismenu": "^1.4.0", "react-metismenu-router-link": "^2.1.0",

AbhaySoningra commented 5 years ago

Hi Alper, I created a basic Metis Menu example with Custom Links. The Menu items (parent and child both) don't seem to be getting activated. (Just the classes to colour them are not being added, Though they open and collapse without any issues) Here below is my code. Can you please help me with what am I missing.

import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import MetisMenu from 'react-metismenu';

class LeftPanel extends Component { constructor(props) { super(props); this.state = { content: [ {
id: 1, icon: 'metismenu-icon fa fa-angle-down', label: 'A', content: [ {
id: 2, icon: 'metismenu-icon fa fa-ruler-combined', label: 'B', to: 'B' }, {
id: 3, icon: 'metismenu-icon fa fa-angle-double-right', label: 'C', to: '#C' }, {
id: 4, icon: 'metismenu-icon fa fa-angle-double-right', label: 'D', to: '#D' }, {
id: 5, icon: 'metismenu-icon fa fa-angle-double-right', label: 'E', to: '#E' }, {
id: 6, icon: 'metismenu-icon fa fa-angle-double-right', label: 'F', to: '#F' }, ] }, {
id: 7, icon: 'metismenu-icon fa fa-angle-down', label: 'G', content: [ {
id: 8, icon: 'metismenu-icon fa fa-angle-double-right', label: 'H', to: '#H' }, ] }, {
id: 9, icon: 'metismenu-icon fa fa-angle-down', label: 'I', content: [ {
id: 10, icon: 'metismenu-icon fa fa-angle-double-right', label: 'J', to: '#WJ' }, ] }, {
id: 11, icon: 'metismenu-icon fa fa-angle-down', label: 'K', content: [ {
id: 12, icon: 'metismenu-icon fa fa-angle-double-right', label: 'L', to: '#L' }, ] }, {
id: 13, icon: 'metismenu-icon fa fa-angle-down', label: 'M', content: [ {
id: 14, icon: 'metismenu-icon fa fa-angle-double-right', label: 'N', to: '#N' }, ] } ] }; }

render() {
    return ReactDOM.render(
        <MetisMenu content={this.state.content} LinkComponent={CustomLink} activeLinkId/>,
        document.getElementById('left')
    );
}

} export default LeftPanel;

class CustomLink extends React.Component { constructor() { super(); this.onClick = this.onClick.bind(this); }

onClick(e) {
    if (this.props.hasSubMenu) { this.props.toggleSubMenu(e); }
    else {
        this.props.activateMe({
            newLocation: this.props.to,
            selectedMenuLabel: this.props.label,
        });
    }
}

render() {
    return (
        <div className="metismenu-link" onClick={this.onClick}>
            {this.props.children}
        </div>
    );
}

};

jpribyl commented 5 years ago

Hey guys, I was able to solve this by holding the active link in state. I am using a hash router so if you are not then you may need to change window.location.hash.replace(/^#/,'') to whatever will match the "to" field of your menu item.

import React, {Component} from 'react';
import MetisMenu from 'react-metismenu';

class MetisSideBar extends React.Component {
  constructor(props) {
    super(props);
    this.state = {activeLinkTo: '/#'};
  }
  componentDidMount() {
    this.setState({
        activeLinkTo: window.location.hash.replace(/^#/,'')
    });
  }
  componentWillReceiveProps() {
    this.setState({
      activeLinkTo: window.location.hash.replace(/^#/,'')
    });
  }

      render() {
        const metisMenuItems = [
                {
                    icon: 'dashboard',
                    label: 'Sigtrans',
                    to: '/'
                },
                {
                    icon: 'bell',
                    label: 'Ocorrencias',
                    content: [
                        {
                            label: 'Abertas',
                            to: 'abertas'
                        }
                    ]
                },
                {
                    icon: 'ambulance',
                    label: 'Ocorrencias fatais',
                    to: 'obitos'
                }
            ];
    return (
        <div id="metisMenu">
          <MetisMenu
            ref="metisMenu"
            content={metisMenuItems}
            activeLinkTo={this.state.activeLinkTo}
          />
        </div>);
}
export default MetisSideBar;

cheers

NatalJR commented 5 years ago

Hey guys, I was able to solve this by holding the active link in state. I am using a hash router so if you are not then you may need to change window.location.hash.replace(/^#/,'') to whatever will match the "to" field of your menu item.

import React, {Component} from 'react';
import MetisMenu from 'react-metismenu';

class MetisSideBar extends React.Component {
  constructor(props) {
    super(props);
    this.state = {activeLinkTo: '/#'};
  }
  componentDidMount() {
    this.setState({
        activeLinkTo: window.location.hash.replace(/^#/,'')
    });
  }
  componentWillReceiveProps() {
    this.setState({
      activeLinkTo: window.location.hash.replace(/^#/,'')
    });
  }

      render() {
        const metisMenuItems = [
                {
                    icon: 'dashboard',
                    label: 'Sigtrans',
                    to: '/'
                },
                {
                    icon: 'bell',
                    label: 'Ocorrencias',
                    content: [
                        {
                            label: 'Abertas',
                            to: 'abertas'
                        }
                    ]
                },
                {
                    icon: 'ambulance',
                    label: 'Ocorrencias fatais',
                    to: 'obitos'
                }
            ];
    return (
        <div id="metisMenu">
          <MetisMenu
            ref="metisMenu"
            content={metisMenuItems}
            activeLinkTo={this.state.activeLinkTo}
          />
        </div>);
}
export default MetisSideBar;

cheers

Will try this as soon as possible!

jpribyl commented 5 years ago

Hey guys, I was able to solve this by holding the active link in state. I am using a hash router so if you are not then you may need to change window.location.hash.replace(/^#/,'') to whatever will match the "to" field of your menu item.

import React, {Component} from 'react';
import MetisMenu from 'react-metismenu';

class MetisSideBar extends React.Component {
  constructor(props) {
    super(props);
    this.state = {activeLinkTo: '/#'};
  }
  componentDidMount() {
    this.setState({
        activeLinkTo: window.location.hash.replace(/^#/,'')
    });
  }
  componentWillReceiveProps() {
    this.setState({
      activeLinkTo: window.location.hash.replace(/^#/,'')
    });
  }

      render() {
        const metisMenuItems = [
                {
                    icon: 'dashboard',
                    label: 'Sigtrans',
                    to: '/'
                },
                {
                    icon: 'bell',
                    label: 'Ocorrencias',
                    content: [
                        {
                            label: 'Abertas',
                            to: 'abertas'
                        }
                    ]
                },
                {
                    icon: 'ambulance',
                    label: 'Ocorrencias fatais',
                    to: 'obitos'
                }
            ];
    return (
        <div id="metisMenu">
          <MetisMenu
            ref="metisMenu"
            content={metisMenuItems}
            activeLinkTo={this.state.activeLinkTo}
          />
        </div>);
}
export default MetisSideBar;

cheers

Will try this as soon as possible!

Let me know how it goes!