Material-ui adding Link component from react-router

94,815

Solution 1

For Material UI 1.0 with Typescript: see this post by @ogglas below.

For Material-UI 1.0 with plain JS:

<Tabs value={value} onChange={this.handleChange}>
  {
    this.props.tabs.map(
      ({label, path})=><Tab key={label} 
                            label={label} 
                            className={classes.tabLink} 
                            component={Link} 
                            to={path} />
    )
  }
</Tabs>

And classes.tabLink is defined as:

tabLink : {
    display:"flex",
    alignItems:"center",
    justifyContent:"center"
}

How this works?

All the mui 1.0 components inheriting from ButtonBase, support a component prop, see ButtonBase. The idea is to allow you to control what the component renders as its wrapper/root element. Tab also has this feature although at the time of writing this answer this prop is not documented explicitly, but as Tab inherits from ButtonBase, all its props carry over (and the documentation does cover this).

Another feature of ButtonBase is that all the extra props, not in use by ButtonBase or inherited component, are spread over the specified component. We have used this behavior to send the to prop used by Link by giving it to Tab control. You can send any additional props in the same way. Note that this is documented explicitly for both ButtonBase and Tab.

Thanks @josh-l for asking this to be added.

Solution 2

here's how you can do it now:

<Tabs onChange={this.changeTab} value={value}>
   <Tab value={0} label="first" containerElement={<Link to="/first"/>} />
   <Tab value={1} label="second" containerElement={<Link to="/second"/>}/>
   <Tab value={2} label="third" containerElement={<Link to="/third"/>} />
 </Tabs>

Solution 3

You can try this simple method

 <Tab label='Most popular ideas'  to='/myPath' component={Link} />

Solution 4

This is solved using the <Link /> from material-ui instead of directly using the <Link /> or <NavLink /> from react-router. The example for the same can be found in the documentation here.

https://material-ui.com/components/links/

Also <Button /> tag has a component prop to achieve this

<Button color="inherit" component={Link} to={"/logout"}>Logout</Button>

An extensive discussion on this can be found here

https://github.com/mui-org/material-ui/issues/850

Solution 5

Since we are using TypeScript I could not use @hazardous solutions. This is how we implemented routing for material-ui v1.0.0-beta.16 and react-router 4.2.0. The reason why we are splitting this.props.history.location.pathname is because we need to access /renewals/123 for example. If we did not do this we would get the following warning and no tab would be displayed as active: Warning: Material-UI: the value provided '/renewals/123' is invalid

Complete code with imports:

import * as React from "react";
import * as ReactDOM from "react-dom";
import * as ReactRouter from "react-router";
import * as PropTypes from "prop-types";
import { Switch, Route, Redirect, Link  } from "react-router-dom";
import { Cases } from './../Cases';
import { SidePane } from './../SidePane';
import { withStyles, WithStyles } from 'material-ui/styles';
import Paper from 'material-ui/Paper';
import Tabs, { Tab } from 'material-ui/Tabs';
import { withRouter } from "react-router-dom";
import Badge from 'material-ui/Badge';
import Grid from 'material-ui/Grid';
import { Theme } from 'material-ui/styles';
import SimpleLineIcons from '../../Shared/SimpleLineIcons'

interface IState {
    userName: string;
}

interface IProps {
    history?: any
}

const styles = (theme: Theme) => ({
    root: theme.typography.display1,
    badge: {
        right: '-28px',
        color: theme.palette.common.white,
    },
    imageStyle:{
        float: 'left',
        height: '40px',
        paddingTop: '10px'
    },
    myAccount: {
        float: 'right'
    },
    topMenuAccount: {
        marginLeft: '0.5em',
        cursor: 'pointer'
    }
});

type WithStyleProps = 'root' | 'badge' | 'imageStyle' | 'myAccount' | 'topMenuAccount';

class Menu extends React.Component<IProps & WithStyles<WithStyleProps>, IState> {
    constructor(props: IProps & WithStyles<WithStyleProps>) {
        super(props);
        this.state = {
            userName: localStorage.userName ? 'userName ' + localStorage.userName : ""
        }
    }
    componentDidMount() {
        this.setState({ userName: localStorage.userName ? localStorage.userName : "" })
    }
    logout(event: any) {
        localStorage.removeItem('token');
        window.location.href = "/"
    }

    handleChange = (event: any, value: any) => {
        this.props.history.push(value);
    };

    render() {
        const classes = this.props.classes;
        let route = '/' + this.props.history.location.pathname.split('/')[1];
        return (
            <div>
                <Grid container spacing={24}>
                    <Grid item xs={12} className={classes.root}>
                        <img src="/Features/Client/Menu/logo.png" alt="Logo" className={classes.imageStyle} />
                        <div className={this.props.classes.myAccount}>
                        <span><span className={this.props.classes.topMenuAccount}>MY ACCOUNT</span><span className={classes.topMenuAccount}><SimpleLineIcons iconName={'user'} />&#x25BE;</span></span>
                            <span onClick={this.logout} className={classes.topMenuAccount}><SimpleLineIcons iconName={'logout'} /></span>
                        </div>
                    </Grid>
                    <Grid item xs={12} >
                        <div className="route-list">
                            <Tabs
                                value={route}
                                onChange={this.handleChange}
                                indicatorColor="primary"
                                textColor="primary"
                            >
                                <Tab label="Overview" value="/" />
                                <Tab label={<Badge classes={{ badge: classes.badge }} badgeContent={this.props.caseRenewalCount} color="primary">
                                    Renewals
                                   </Badge>} value="/renewals" />
                            </Tabs>
                        </div>
                    </Grid>
                </Grid>
            </div>
        );
    }
}
export default withStyles(styles)(withRouter(Menu))
Share:
94,815
Evaldas Buinauskas
Author by

Evaldas Buinauskas

Updated on December 22, 2021

Comments

  • Evaldas Buinauskas
    Evaldas Buinauskas over 2 years

    I'm struggling to add <Link/> component to my material-ui AppBar

    This is my navigation class:

    class Navigation extends Component {
      constructor(props) {
        super(props)
      }
    
      render() {
        var styles = {
          appBar: {
            flexWrap: 'wrap'
          },
          tabs: {
            width: '100%'
          }
        }
    
        return (
          <AppBar showMenuIconButton={false} style={styles.appBar}>
            <Tabs style={styles.tabs}>
              <Tab label='Most popular ideas'/>
              <Tab label='Latest ideas' />
              <Tab label='My ideas' />
            </Tabs>
          </AppBar>
        )
      }
    }
    

    Which looks okay: Navbar

    Tabs are clickable, have fluid animations, that's cool. But how do I wire them up together with react-router and its' <Link/> component?

    I've tried adding onChange listener like that:

    <Tab
      label='My ideas'
      onChange={<Link to='/myPath'></Link>}
    />
    

    However I'm getting following error:

    Uncaught Invariant Violation: Expected onChange listener to be a function, instead got type object
    

    If I try to wrap <Tab/> component into <Link/> component, I'm getting error that <Tabs/> component accepts only <Tab/> component.

    This doesn't work either (no error is being produced, but clicking on Tab does not bring me to the path):

    <Tab label='Most popular ideas'>
      <Link to='/popular'/>
    </Tab>
    

    How do I make <Link/> component work together with <Tabs> and <AppBar>? If that's not possible, I can use any other component from material-ui library to form a proper menu.

  • Evaldas Buinauskas
    Evaldas Buinauskas about 8 years
    This seems alright to me. I'll try to add it to my code.
  • U Avalos
    U Avalos about 7 years
    Sweet. But I noticed that the docs don't say this. Do all elements support containerElement?
  • high incompetance
    high incompetance about 7 years
    the ones I tried (that made sense for them to have it) they did have it
  • paws
    paws about 7 years
    At time of writing this technique doesn't appear to work on material-ui@next.
  • Josh L
    Josh L over 6 years
    Can you be more specific on what the arguments being passed in for the "component" and "to" apis are? I was looking at the material UI v1 api tab documentation and it's not showing either of these? material-ui-1dab0.firebaseapp.com/api/tab
  • hazardous
    hazardous over 6 years
    For most of the mui container components, you can send a component prop to make it use a different React component instead of the default one. In this example, we are making Tab render the react router Link control. In this mode, the mui component will pass any extra props to that component. As Link requires a to prop, I am passing that in Tab. You will find this behavior documented somewhere in mui. Will try to update my answer soon.
  • Josh L
    Josh L over 6 years
    Awesome thanks! Got it working! But yeah perhaps updating the answer with a bit more detail could help others. Upvoted it either way!
  • Bryce
    Bryce over 5 years
    This worked for me. However, is there anyway to remove the 'blue underline' link styling?
  • Arkhitech
    Arkhitech over 4 years
    This should be the correct answer now with latest material-ui 4+
  • Todd
    Todd over 4 years
    Still a simpler solution in 2020
  • Silvan Mudbind
    Silvan Mudbind about 4 years
    throws Error: React.Children.only expected to receive a single React element child. for me
  • Alan Pallath
    Alan Pallath almost 4 years
    No error is thrown but it is not working for me. running 4.11 core ui
  • RVACode
    RVACode over 3 years
    Thank you, this is working great and should be the correct answer.
  • Italik
    Italik almost 3 years
    Looks fine. There is one problem - how to set initial state of tabs? You use "0", so it's path to "/home", but what if user will start with "/dev" path?