Material-ui: [MenuItem] Links within Menu items

Created on 6 Jan 2015  ·  43Comments  ·  Source: mui-org/material-ui

Hi, I was looking at the current implementation of menu items and noticed that there is no actual link (meaning, html <a> tag). As a result, for instance, even in the navigation bar there is no actual link, and transitions to other pages are managed via onclick events.

I think having links would be an improvement, especially considering server-side rendered pages.

Do you think this make sense, or do you have specific reasons to not go in this direction?

v0.x

Most helpful comment

Use the containerElement prop!

see:
http://stackoverflow.com/questions/32106513/material-ui-menu-using-routes/34507786#34507786

<MenuItem
  containerElement={<Link to="/profile" />}
  primaryText="Profile"
  leftIcon={
    <FontIcon className="material-icons">people</FontIcon>
  } />

All 43 comments

I'm wondering about this as well. I'm using server-side rendering in my application, this is something to look into.

Using anchor elements with href attributes would also improve accessibility and allow people to use browser behavior they are accustomed to (e.g. ⌘-Click to open in a new tab).

+1

+1

+1

+1

+1

+1

+1

@ecesena do you think this would still be an issue with the new MenuItem component? See here. The MenuItems can be surrounded by <a>..</a>, no?

this is possible with what @shaurya947 suggested.

I'm attempting to use MenuItems in my LeftNav like this:

        let menuItems = (
            <div>
                <MenuItem primaryText="Doors"           leftIcon={<FontIcon className="material-icons" color={GlobalStyles.default.activeColor}>home</FontIcon>} /></a>
                <MenuItem primaryText="Load"           leftIcon={<FontIcon className="material-icons" color={GlobalStyles.default.activeColor}>home</FontIcon>} />
                <MenuItem primaryText="Notes"           leftIcon={<FontIcon className="material-icons" color={GlobalStyles.default.activeColor}>home</FontIcon>} />
                <MenuItem primaryText="Alerts"          leftIcon={<FontIcon className="material-icons" color={GlobalStyles.default.activeColor}>home</FontIcon>} />
                <MenuItem primaryText="Admin"           leftIcon={<FontIcon className="material-icons" color={GlobalStyles.default.activeColor}>home</FontIcon>} />
            </div>
        );

        return (
            <LeftNav
                ref="leftNav"
                docked={false}
                onChange={this._onLeftNavChange}
            >
                {menuItems}
            </LeftNav>
        )

Wrapping a <a> around a <MenuItem> tag throws a warning validateDOMNesting(...): <a> cannot appear as a descendant of <a>. See MainLayout > a > ... > MenuItem > ListItem > EnhancedButton > a"

If you do a React inspection of the output you get the following.

<a href="/">
    <MenuItem..>
        <ListItem..>
            <div>
                <EnhancedButton..>
                    <a>
                        <TouchRipple ..>
                    </a>
                </EnhancedButton>
            </div>
        </ListItem>
    </MenuItem>
</a>

which having nested <a> elements is offensive.

<MenuItems> generate <a> elements within the Button declaration. What prop is used from MenuItem to populate the href attribute within the generated <EnhancedButton>?

Using a MenuItem object as a prop for LeftNav gets the linking working, but I have not been able to figure out how to add left/right Icons this way.

ie:

let menuItems = [
         { route: '/load_areas/'+ this.state.area + '/doors', text: 'Doors' },
         { route: '/load_areas/'+ this.state.area + '/trailer_loads', text: 'Load' },
         { route: 'notes', text: 'Notes' },
         { route: 'alerts', text: 'Alerts' },
         { route: 'admin', text: 'Admin' }
         ];

<LeftNav
                ref="leftNav"
                docked={false}
                menuItems={menuItems}
                onChange={this._onLeftNavChange}
            />

this is a separate issue, please open one, so we can label it and add it to milestones, tnx :grin:

Use the containerElement prop!

see:
http://stackoverflow.com/questions/32106513/material-ui-menu-using-routes/34507786#34507786

<MenuItem
  containerElement={<Link to="/profile" />}
  primaryText="Profile"
  leftIcon={
    <FontIcon className="material-icons">people</FontIcon>
  } />

Now the prop linkButton of EnhancedButton is deprecated. LinkButton is no longer required when the href property is provided. It will be removed with v0.16.0.

For example:

<MenuItem primaryText="Primary Text" href="/your/link" />

href reloads page. how about if i want to use react router

@cezarneaga try href="#/your/link"

<MenuItem
  containerElement={<Link to="/profile" />}
.../>

documentation mentions you need to enter also linkButton. if you remove this, it works without errors.

@DaxChen I saw your stackoverflow updated answer for Dec 2016(Extremely Recent haha) and I was wondering if you knew if this worked for a MenuItem within a Drawer? I'm trying the following below and have been totally unable to get Link working with the MenuItem as containerElement seems to do nothing.

<Drawer
  docked={false}
  width={300}
  onRequestChange={this.closeDrawer}
  open={this.state.open}>
  <AppBar title="Title"
 />
  <MenuItem primaryText="home" containerElement={<Link to="/home" />} />
</Drawer>

containerElement works but isn't documented, so I guess this thread will have to serve as the official documentation for now. Thanks!

Hi @Faolain !

Really sorry for the late reply...
I tried you code on an clean app with create-react-app, but everything seemed working okay?!
Maybe you configured react-router differently, or is the version different?

Anyway, here's an Example Repo, and the Live Demo Here.

If you still can't find the cause of your problem, can you provide a sample repo to reproduce?

None of the solutions above worked on Safari and iOS. So here's what I did as a work around for react-router

          <MenuItem
            onTouchTap={() => {
              this._yourMethod()
              browserHistory.push('/howItWorks')
            }}
            primaryText="Menu Link"
          />

@janzenz is this example here not working in your browser?

I tried it in Safari on macOS and Safari on iOS and they all work. Is this not working for you?

Although your workaround works, it may cause bad SEO and you'll lose some native behavior such as link-preview and option-click to open in new tab.

@DaxChen thanks for the insights. I'm not sure if my issue is related with your first link. I've already tried the 2nd one but once I add a onTouchTap prop the <Link /> doesn't work anymore.

@janzenz I've added onTouchTap prop in my previous example and it still works.
Don't know if your setting is different somewhere or something... 😢
You can check out the updated code and see if it works for you?

@DaxChen I see you're using material-ui at version ~0.16.0. Have you tried the latest ~0.17.0 and see if that is still not a problem?

@janzenz I was using an older version, just updated to the latest 0.17.4 and it still works!

Touché! Probably my setup then. Thanks for confirming that @DaxChen I'll get back to this thread if I have found the real issue.

@DaxChen , I tried the example you gave me, but it fails

Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in. Check the render method of `EnhancedButton`.
    at invariant (invariant.js:44)
    at ReactCompositeComponentWrapper.instantiateReactComponent [as _instantiateReactComponent] (instantiateReactComponent.js:74)
    at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:367)
    at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactDOMComponent.mountChildren (ReactMultiChild.js:238)
    at ReactDOMComponent._createInitialChildren (ReactDOMComponent.js:697)
    at ReactDOMComponent.mountComponent (ReactDOMComponent.js:516)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:371)
    at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:371)
    at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactDOMComponent.mountChildren (ReactMultiChild.js:238)
    at ReactDOMComponent._createInitialChildren (ReactDOMComponent.js:697)
    at ReactDOMComponent.mountComponent (ReactDOMComponent.js:516)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:371)
    at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactDOMComponent.mountChildren (ReactMultiChild.js:238)
    at ReactDOMComponent._createInitialChildren (ReactDOMComponent.js:697)
    at ReactDOMComponent.mountComponent (ReactDOMComponent.js:516)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:371)
    at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactDOMComponent.mountChildren (ReactMultiChild.js:238)
    at ReactDOMComponent._createInitialChildren (ReactDOMComponent.js:697)
    at ReactDOMComponent.mountComponent (ReactDOMComponent.js:516)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:371)
    at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:371)
    at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:371)
    at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:371)
    at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:371)
    at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:371)
    at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:371)
    at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:371)
    at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
    at Object.mountComponent (ReactReconciler.js:46)
    at mountComponentIntoNode (ReactMount.js:104)
    at ReactReconcileTransaction.perform (Transaction.js:140)
    at batchedMountComponentIntoNode (ReactMount.js:126)
    at ReactDefaultBatchingStrategyTransaction.perform (Transaction.js:140)
    at Object.batchedUpdates (ReactDefaultBatchingStrategy.js:62)
    at Object.batchedUpdates (ReactUpdates.js:97)
    at Object._renderNewRootComponent (ReactMount.js:320)
    at Object._renderSubtreeIntoContainer (ReactMount.js:401)
    at Object.render (ReactMount.js:422)
    at Object.<anonymous> (index.js:36)
    at __webpack_require__ (bootstrap e3e2367…:555)
    at fn (bootstrap e3e2367…:86)
    at Object.<anonymous> (bootstrap e3e2367…:578)
    at __webpack_require__ (bootstrap e3e2367…:555)
    at bootstrap e3e2367…:578
    at bootstrap e3e2367…:578

My code is available at https://github.com/hhimanshu/spicyveggie/tree/cards (cards branch)

I am not sure what is different, but it just does not work

@janzenz
I also tried browserHistory.push('/howItWorks') and imported import browserHistory from 'react-router-dom', but got undefuned.

@Oduig,
I tried #/your/link, but it does not take me anywhere
I tried /your/link, but it reloads tha page and I lose Drawer from Material-UI

None of these things worked for me. My code is available at https://github.com/hhimanshu/spicyveggie/tree/cards (cards branch), specific commit is https://github.com/hhimanshu/spicyveggie/commit/844b7b7cddf9102995cd2680a783df3b6ef48537, Could someone please help?

@hhimanshu change this line to

import { Link } from 'react-router-dom'

more info on MDN

@DaxChen, thanks. That was it. However, not when my page loads with {<Link to="/menu"/>}, it loads the entire page and my Drawer is gone.

screen shot 2017-04-24 at 1 54 23 pm

@hhimanshu that's because you used only the Menu component in your \menu route, so App won't be rendered.

Nest your Menu and Summary inside App or some layout components, and pass in respective children.

You can use react-devtools to check the rendered component hierarchy and its states!

This is not related to material-ui, please read more in react-router's tutorials/docs.

+1

Workarounds for links HANDLED & NOT HANDLED by React Router.


If target link is handled by React Router

MenuItemLink.js

import React from 'react';
import { MenuItem } from 'material-ui/Menu';
import { Route } from 'react-router-dom';

class MenuItemLink extends React.Component {
  render() {
    const {
      to, also,
      ...rest
    } = this.props;

    return (
      <Route
        render={({ history, location }) => (
          <MenuItem
            onClick={() => {
              history.push(to);
              if (typeof also === 'function') {
                also();
              }
            }}
            {...rest}
          />
        )}
      />
    );
  }
}
MenuItemLink.muiName = 'MenuItem';
export default MenuItemLink;

Sample Code:

import MenuItemLink from './MenuItemLink';
<Menu ... >
        <MenuItemLink to="/users"
          also={this.handleRequestClose}>Users</MenuItemLink>
</Menu>

If target link is NOT handled by React Router, ie: request is forwarded to an API server or whatever

onClick function:

    function facebookLoginRedirect () {
      if (window) window.location.href = "/api/auth/facebook"
      return true;
    }

Sample Code:

<MenuItem onClick={ facebookLoginRedirect }>Login with Facebook</MenuItem>

I'm coming to add to this, in case someone else stumbled upon it as well.

On v3.4, you can achieve this like so:

import { Link } from 'react-router-dom';
import MenuItem from '@material-ui/core/MenuItem';

...

<MenuItem component={Link} to="/your-path">...</MenuItem>

The only solution that worked for me without breaking the Menu layout was:
```javascript


Notifications



Profile


The only solution that worked for me without breaking the Menu layout was:

<MenuList>
  <Link to='/your-path' style={{ textDecoration: 'none' }}>
    <MenuItem>
      Notifications
     </MenuItem>
  </Link>
  <Link to='/your-path' style={{ textDecoration: 'none' }}>
    <MenuItem>
      Profile
     </MenuItem>
  </Link>
</MenuList>

@eladlevy This would end up in a poor semantics HTML.

<ul>
  <a><li/></a>
  <a><li/></a>
</ul>

Leaving this here in case it helps anyone (running v3.9.0). I needed a <Select> to act as a menu of react-router-dom <Link>s. Each link corresponded to a language setting, so the dropdown would display the value of the current language from the query parameter.

<Select value={currentLanguage}>
  {languages.map(language => (
    <ListItem
      button
      component={btnProps => (
        <Link
          to={ `/things?lang=${ language }` }
          {...btnProps as any}
        />
      )}
      value={language}
      key={language}
    >
      {language}
    </ListItem>
  ))}
</Select>

This was the cleanest solution I could find that required minimal TypeScript casting and maintained visuals consistent with Material UI.

@goyney has the solution, thanks

I'm coming to add to this, in case someone else stumbled upon it as well.

On v3.4, you can achieve this like so:

import { Link } from 'react-router-dom';
import MenuItem from '@material-ui/core/MenuItem';

...

<MenuItem component={Link} to="/your-path">...</MenuItem>

Working and clean solution, thanks ❤️

For external links you'll want to use component="a", but also surround the MenuItem in <li>:

<MenuList>
    <li>
        <MenuItem
            component="a"
            href="https://google.com"
            target="_blank"
        >Google</MenuItem>
    </li>
</MenuList>

This generates the following HTML:

<ul class="MuiList-root MuiList-padding" role="menu" tabindex="-1">
    <li>
        <a class="[...]" tabindex="-1" aria-disabled="false" role="menuitem" href="https://google.com" target="_blank">Google</a>
    </li>
</ul>

Suppose I have a popup menu and use

import { Link } from 'react-router-dom';
import MenuItem from '@material-ui/core/MenuItem';

\\

The Link will bring up the /your-path page, how would one close the popup menu?

Was this page helpful?
0 / 5 - 0 ratings