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 ListItem
s.
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);
I figured out, I can wrap ListItem
in a Link
as follow:
<Link to="users" style={{ textDecoration: 'none' }}>
<ListItem button >
<ListItemIcon>
<Person />
</ListItemIcon>
<ListItemText primary={<FormattedMessage id="user" />} />
</ListItem>
</Link>
With this approach, the onClick method will be fired too.
Any suggestions or imporvements?
I figured out, I can wrap ListItem in a Link as follow
This solution isn't semantically valid. Have a look at how we handle the issue on the documentation:
You can also try this one
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>
);
}
I don't see it in the docs:
https://material-ui-next.com/demos/lists/
https://material-ui-next.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?
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...)
@ilyador This UI link style issue has been solved.
@zhangwei900808 No, that's still wrong semantically, it generates <ul><a ..>...
https://codesandbox.io/s/lpwq74p30m
Just use the answer provided above
@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)
@caub Why are you introducting an extra Button component? You can use the component property on the MenuItem.
@oliviertassinari because ul > a
isn't valid html5 https://codesandbox.io/s/lpwq74p30m
@caub nav > a
is valid.
Ok, thanks, fine https://codesandbox.io/s/lpwq74p30m
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
**{...{ to: menu.link }}**
component={Link}
button={true}
>
import {Link} from 'react-router-dom' <ListItem component={Link} to="/" button> // or <ListItem component={props => <Link to="/" {...props} />} button>
@mehrdaad @oliviertassinari It's worked^_^
This was perfect for me. Thanks!
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>
@HugoGresse Yes, you need to use the forward ref API. You should be able to find examples in the documentation. I have updated https://github.com/mui-org/material-ui/issues/7956#issuecomment-326908167 with an up-to-date answer. Let us know if it's OK.
@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.
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.
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
key={index}
**{...{ to: menu.link }}**
component={Link}
button={true}
>
button={true}
simply makes the UI behavior of drawers the same as no link.
For others trying to do this in Typescript:
A simple example using the ListItem component property to specify a
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>
```
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>
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 :-)
Most helpful comment
This is covered in the documentation: https://material-ui.com/components/buttons/#third-party-routing-library.