Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert ListItem compatible with react-router #7956

Closed
mehrdad-shokri opened this issue Aug 30, 2017 · 21 comments
Closed

Convert ListItem compatible with react-router #7956

mehrdad-shokri opened this issue Aug 30, 2017 · 21 comments
Labels
support: question Community support but can be turned into an improvement

Comments

@mehrdad-shokri
Copy link

mehrdad-shokri commented Aug 30, 2017

I want to make each ListItem in a List to a Link component.

I have a Drawer component composed of a List component,
That List is composed of 4 ListItems.
What I want to achieve is that convert each ListItem to a React router Link.
I tried to modify drawer's onClick method, get Id of the clicked ListItem and then programmatically change location of window based on the id.
But I do think this approach is more jqueryish that the react concepts.
I also tried to add Link to component prop in ListItem, but it resulted error.( I've seen you have used a in component prop in docs).
What is the recommended way to achieve this requirement?

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import Drawer from 'material-ui/Drawer';
import List, { ListItem, ListItemIcon, ListItemText } from 'material-ui/List';
import Face from 'material-ui-icons/Face';
import Person from 'material-ui-icons/Person';
import Assignment from 'material-ui-icons/Assignment';
import Link from 'react-router-dom/Link';
import { FormattedMessage } from 'react-intl';

const styles = {
   list: {
      width: 250,
      flex: 'initial',
    },
};

 class UndockedDrawer extends Component {

   constructor() {
      super();
      this.onClick = this.onClick.bind(this);
    }

    onClick(e, data) {
       console.log(data);
       this.props.onRequestClose();
     }

   render() {
       const classes = this.props.classes;
        const sidebarListItems = (
     <div>
         <ListItem button >
            <ListItemIcon>
               <Person />
           </ListItemIcon>
            <ListItemText primary={<FormattedMessage id="user" />} />
          </ListItem>
    <ListItem button>
      <ListItemIcon>
        <Assignment />
      </ListItemIcon>
      <ListItemText primary={<FormattedMessage id="consultant" />} />
    </ListItem>
    <ListItem button>
      <ListItemIcon>
        <Face />
      </ListItemIcon>
      <ListItemText primary={<FormattedMessage id="student" />} />
    </ListItem>
  </div>
);

const sidebarList = (
  <div>
    <List className={classes.list} >
      {sidebarListItems}
    </List>
  </div>
);

return (
  <div>
    <Drawer
      anchor="right"
      open={this.props.open}
      onRequestClose={this.props.onRequestClose}
      onClick={this.onClick}
    >
      {sidebarList}
    </Drawer>
   </div>
   );
   }
 }

  UndockedDrawer.propTypes = {
     classes: PropTypes.shape({}).isRequired,
     open: PropTypes.bool.isRequired,
     onRequestClose: PropTypes.func.isRequired,
    };

   export default withStyles(styles)(UndockedDrawer);
  • Material-UI: 1.0.6-beta
  • React: ^15.6.1
  • Browser: Chrome: 60
  • react-router-dom : ^4.1.2
@mehrdad-shokri

This comment has been minimized.

@oliviertassinari oliviertassinari added support: question Community support but can be turned into an improvement v1 labels Aug 30, 2017
@oliviertassinari

This comment has been minimized.

@zhangwei900808
Copy link

zhangwei900808 commented Sep 4, 2017

This is covered in the documentation: https://material-ui.com/components/buttons/#third-party-routing-library.

import React from 'react';
import { MemoryRouter as Router } from 'react-router';
import { Link, LinkProps } from 'react-router-dom';
import ListItem from '@material-ui/core/ListItem';
import { Omit } from '@material-ui/types';

// The usage of React.forwardRef will no longer be required for react-router-dom v6.
// see https://github.com/ReactTraining/react-router/issues/6056
const AdapterLink = React.forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => (
  <Link innerRef={ref as any} {...props} />
));

const CollisionLink = React.forwardRef<HTMLAnchorElement, Omit<LinkProps, 'innerRef' | 'to'>>(
  (props, ref) => <Link innerRef={ref as any} to="/getting-started/installation/" {...props} />,
);

export default function ButtonRouter() {
  return (
    <Router>
      <ListItem color="primary" component={AdapterLink} to="/">
        Simple case
      </ListItem>
      <ListItem component={CollisionLink}>Avoids props collision</ListItem>
    </Router>
  );
}

bennycode added a commit to bennycode/website that referenced this issue Oct 12, 2017
@alexilyaev
Copy link

alexilyaev commented Nov 10, 2017

I don't see it in the docs:
https://material-ui.com/demos/lists/
https://material-ui.com/demos/drawers/

This is a common thing when using React Router, but it's not clear how to use NavLink in a ListItem.
A Button with component prop?
And the button props and the component props are mixed together on the same JSX tag?
What is this sort of mixin?
Why so complex?

@ilyador
Copy link

ilyador commented Nov 16, 2017

When using @mehrdaad 's solution the links look like normal MUI list, but when using component={Link} as @oliviertassinari suggested, the style gets messed up:
(In the image the mouse is hovering over "Build" but the second item gets the underline...)

screen shot 2017-11-16 at 12 09 05

@oliviertassinari

This comment has been minimized.

@caub
Copy link
Contributor

caub commented Feb 1, 2018

@zhangwei900808 No, that's still wrong semantically, it generates <ul><a ..>...
https://codesandbox.io/s/lpwq74p30m

Just use the answer provided above

@caub
Copy link
Contributor

caub commented Feb 2, 2018

@oliviertassinari Would there be an easier way to do:

const styles = {
	li: {
		'&:hover': {
			backgroundColor: 'transparent',
		},
	},
};
// ...
<MenuItem disableGutters className={classes.li}>
    <Button component={Link} to="/logout" ...

To avoid a double hover effect, on the MenuItem and on Button (which still have a slight gutter/padding, another issue)
Because this is cumbersome

demo: https://codesandbox.io/s/nk834yk5pl (not using component={Link} to="/..." but that's the same)

@oliviertassinari
Copy link
Member

@caub Why are you introducting an extra Button component? You can use the component property on the MenuItem.

@caub
Copy link
Contributor

caub commented Feb 2, 2018

@oliviertassinari because ul > a isn't valid html5 https://codesandbox.io/s/lpwq74p30m

@oliviertassinari
Copy link
Member

oliviertassinari commented Feb 2, 2018

@caub nav > a is valid.

@caub
Copy link
Contributor

caub commented Feb 2, 2018

Ok, thanks, fine https://codesandbox.io/s/lpwq74p30m

@pushpanjalip
Copy link

pushpanjalip commented Oct 22, 2018

Here is the solution that solved my issue

<List>
        {menus.map((menu: any, index: any) => {
          return (
            <ListItem
              key={index}
              {...{ to: menu.link }}
              component={Link}
              button={true}
            >
              <ListItemIcon>{menu.icon}</ListItemIcon>
              <ListItemText primary={menu.text} />
            </ListItem>
          );
        })}
      </List>

Thus just use ListItem as
<ListItem
key={index}
{...{ to: menu.link }}
component={Link}
button={true}
>

@kentferolino

This comment has been minimized.

@HugoGresse
Copy link

HugoGresse commented Aug 6, 2019

The most voted answer failed after the MUI v4 migration (I'm on 4.3.1) due to "Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?"

I've replaced the NavLink component to a wrapper like so:

import React, { Component } from "react"
import { NavLink } from "react-router-dom"

/**
 * React Router Nav Link wrapper to forward the ref to fix "Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?"
 *
 * From https://material-ui.com/guides/composition/#caveat-with-refs
 */
class NavLinkMui extends Component {
    render() {
        const { forwardedRef, ...props } = this.props
        return <NavLink {...props} ref={forwardedRef} />
    }
}

export default NavLinkMui

Usage:

<ListItem
    button
    component={NavLinkMui}
    to='/'
>
    <ListItemIcon>
        <SlideshowIcon />
    </ListItemIcon>
</ListItem>

@oliviertassinari
Copy link
Member

@HugoGresse Yes, you need to use the forward ref API. You should be able to find examples in the documentation. I have updated #7956 (comment) with an up-to-date answer. Let us know if it's OK.

@HugoGresse
Copy link

HugoGresse commented Aug 6, 2019

@oliviertassinari it did.

I'm still having some issue with either the forwardRef solution or Component one where the Button text color is not taking into account the theme palette color. The text color should be white to match palette.type: 'dark', but is white.

Probably linked to a change on MUI because without the component props I'm still having the issue.
Capture d'écran 2019-08-06 16 54 51

For those reading up here, you can pass additional props to the ListItemText...

<ListItemText
    primaryTypographyProps={{
        color: 'textPrimary'
    }}
    primary="Dashboard"
/>

BTW, your example is now in TS.

@zhangzw16
Copy link

Here is the solution that solved my issue

<List>
        {menus.map((menu: any, index: any) => {
          return (
            <ListItem
              key={index}
              {...{ to: menu.link }}
              component={Link}
              button={true}
            >
              <ListItemIcon>{menu.icon}</ListItemIcon>
              <ListItemText primary={menu.text} />
            </ListItem>
          );
        })}
      </List>

Thus just use ListItem as
<ListItem
key={index}
{...{ to: menu.link }}
component={Link}
button={true}

button={true} simply makes the UI behavior of drawers the same as no link.

@yoDon
Copy link

yoDon commented Nov 18, 2019

For others trying to do this in Typescript:

A simple example using the ListItem component property to specify a element as the ListItem implementation. The trick in Typescript is to use the spread operator (...) on the props of the ListItem to sneak past the unwanted and unneeded Typescript errors that specifying this type of component and "to" properties for the ListItem would normally bring about (Typescript error TS2769 Property 'component' does not exist on type 'IntrinsicAttributes...)

import { NavLink } from "react-router-dom";

        <List>
            <ListItem button={true} {...{ component: NavLink, to: "/Somewhere" }}>
                <ListItemIcon>
                    <ShoppingCartIcon />
                </ListItemIcon>
                <ListItemText primary="Orders" />
            </ListItem>
        </List>

or alternatively if you just want to use an onClick function on the ListItem for example:

    <List>
        <ListItem button={true} {...{ onClick: () => alert("foo") }}>
            <ListItemIcon>
                <DashboardIcon />
            </ListItemIcon>
            <ListItemText primary="Dashboard" />
        </ListItem>
    </List>

@jamime
Copy link

jamime commented Feb 11, 2020

This worked for me

const NavLinkMui = React.forwardRef((props, ref) => (
  <NavLink {...props} activeClassName="Mui-selected" ref={ref} />
))
<ListItem button component={NavLinkMui} to={to}>{text}</ListItem>

@kolagrey
Copy link

The most voted answer failed after the MUI v4 migration (I'm on 4.3.1) due to "Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?"

I've replaced the NavLink component to a wrapper like so:

import React, { Component } from "react"
import { NavLink } from "react-router-dom"

/**
 * React Router Nav Link wrapper to forward the ref to fix "Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?"
 *
 * From https://material-ui.com/guides/composition/#caveat-with-refs
 */
class NavLinkMui extends Component {
    render() {
        const { forwardedRef, ...props } = this.props
        return <NavLink {...props} ref={forwardedRef} />
    }
}

export default NavLinkMui

Usage:

<ListItem
    button
    component={NavLinkMui}
    to='/'
>
    <ListItemIcon>
        <SlideshowIcon />
    </ListItemIcon>
</ListItem>

Thanks @HugoGresse This works perfectly well :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
support: question Community support but can be turned into an improvement
Projects
None yet
Development

No branches or pull requests