Material-ui добавляет компонент Link из реактивного маршрутизатора

Я изо всех сил пытаюсь добавить компонент <Link/> в панель приложения-материалов

Это мой класс навигации:

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>
    )
  }
}

Что выглядит хорошо: Navbar

Вкладки кликабельны, имеют плавную анимацию, это круто. Но как мне соединить их с react-router и его компонентом <Link/>?

Я попытался добавить слушатель onChange так:

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

Однако я получаю следующую ошибку:

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

Если я пытаюсь обернуть компонент <Tab/> компонент <Link/>, я получаю сообщение об ошибке, что компонент <Tabs/> принимает только компонент <Tab/>.

Это тоже не работает (никакой ошибки не возникает, но нажатие на Tab не приводит меня к пути):

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

Как заставить компонент <Link/> работать вместе с <Tabs> и <AppBar>? Если это невозможно, я могу использовать любой другой компонент из библиотеки material-ui чтобы сформировать правильное меню.

Ответ 1

Для Material UI 1.0 с Typescript: см. Этот пост @ogglas ниже.

Для Material-UI 1.0 с простым 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>

И classes.tabLink определяется как:

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

Как это работает?

Все компоненты mui 1.0, наследуемые от ButtonBase, поддерживают опору component, см. ButtonBase. Идея состоит в том, чтобы позволить вам контролировать то, что компонент отображает как его элемент оболочки/корня. Tab также есть эта функция, хотя на момент написания этого ответа эта опора явно не документирована, но, поскольку Tab наследует от ButtonBase, все ее реквизиты переносятся (и документация действительно охватывает это).

Еще одной особенностью ButtonBase является то, что все дополнительные реквизиты, не используемые ButtonBase или унаследованным компонентом, распространяются на указанный component. Мы использовали это поведение, чтобы отправить to проп используется Link, давая его Tab управления. Вы можете отправить любые дополнительные реквизиты таким же образом. Обратите внимание, что это задокументировано явно как для ButtonBase и для Tab.

Спасибо @josh-l за то, что попросили добавить это.

Ответ 2

вот как вы можете это сделать сейчас:

<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>

Ответ 3

Поскольку мы используем TypeScript, я не мог использовать решения @hazardous. Так мы реализовали маршрутизацию для material-ui v1.0.0-beta.16 и material-ui v1.0.0-beta.16 react-router 4.2.0. Причина, по которой мы разделяем this.props.history.location.pathname заключается в том, что нам нужен, например, доступ к /renewals/123. Если бы мы этого не сделали, мы получили бы следующее предупреждение, и никакая вкладка не была бы отображена как активная: Warning: Material-UI: the value provided '/renewals/123' is invalid

Полный код с импортом:

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))

Ответ 4

Вы можете попробовать этот простой метод

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

Ответ 5

Итак, моя работа для этого решения была достаточно надежной, хотя она может быть более ручной для решения, чем то, что вы хотите сделать.

Стратегия, которую я использовал, состоит в том, чтобы на самом деле даже не использовать компонент Link. Вместо этого вы будете использовать свойство Tabs onChange в качестве обратного вызова, которое может реагировать на нажатия Tab и отслеживать местоположение вручную с помощью реквизитов на родительском устройстве.

Вы можете импортировать утилиту "История" из "реактивного маршрутизатора", которая позволит вам вручную удалять местоположения. При использовании React-Router ваше дерево компонентов будет иметь доступ к Location prop, у которого есть ключ пути с строкой вашего текущего местоположения.

Мы вручную проанализируем эту строку в компонентах, составляющих ваш текущий URL-адрес, а затем используем оператор Switch, чтобы определить, какая вкладка выбрана в данный момент, а также ссылку на нее при нажатии на вкладку. (Это дает вам достаточный контроль над навигацией)

(например, ['', 'latest'])

Вот макет того, как ваш компонент может выглядеть после интеграции этого решения.

import React from 'react';
import {History} from 'react-router';

function parseLocation(location) {
    if (String(location)) {
        var locationArray = location.split('/');
        return locationArray;
    } else {
        return false;
    }
};
function filterPath(path) {
    let locationArray = parseLocation(path);
    return locationArray[locationArray.length - 1];
};
var Navigation = React.createClass({
      mixins: [History],
      getPage() {
        if (this.props.location.pathname) {
          let pathname = this.props.location.pathname;
          let pageName = filterPath(pathname);
          return pageName;
        } else {
          return false;
        } 
      },
      decideContent() {
        let page = this.getPage();
        let content;
        switch(page) {
           case 'popular':
              content = 0;
           case 'latest':
              content = 1;
           case 'myideas':
              content = 2;
           default:
              content = 0;
        }
        return content;
      },
      handleTabChange(value) {
        let location = false;
        switch (value) {
           case 0:
             location = 'popular';
             break;
           case 1:
             location = 'latest';
             break;
           case 2:
             location = 'myideas';
             break;
        }
        if (location && location !== this.getPage()) {
          this.history.pushState(null, '/'+location);
        }
      },
      render() {
         var styles = {
          appBar: {
           flexWrap: 'wrap'
          },
          tabs: {
           width: '100%'
          }
         };
         let content = this.decideContent();
         let tabs = <Tabs
                  onChange={this.handleTabChange}
                  value={content}
                >
                  <Tab label="Most Popular Ideas" value={0}  />
                  <Tab label="Latest Ideas" value={1}  />
                  <Tab label="My Ideas" value={2}  />
                </Tabs>;
        return (
         <AppBar showMenuIconButton={false} style={styles.appBar}>
           {tabs}
         </AppBar>
        );
      }
});

Ответ 6

TypeScript реализация вкладок, управляемых маршрутизатором.

Для тех, кто ищет реализацию TypeScript. Легко настраивается. Управляется настройками tabs.

interface ITabsPageProps {
  match: match<{page: string}>;
  history: History;
}

const tabs = [{
  label: 'Fist Tab',
  link: 'fist-tab',
  component: <FirstTabContent/>
}, {
  label: 'Second Tab',
  link: 'second-tab',
  component: <SecondTabContent/>
}, {
  label: 'Third Tab',
  link: 'third-tab',
  component: <ThirdTabContent/>
}];

export class TabsPage extends React.Component<ITabsPageProps> {
  handleChange(tabLink: string) {
    this.props.history.push('/tabs-page/${tabLink}');
  }

  render() {
    const currentTab = this.props.match.params.page;
    const selectedTab = tabs.find(tab => currentTab === tab.link);

    return (
      <Fragment>
        <Tabs
          value={currentTab}
          onChange={(event, value) => this.handleChange(value)}
        >
          {tabs.map(tab => (
            <Tab
              key={tab.link}
              value={tab.link}
              label={tab.label}
            />
          ))}
        </Tabs>

        {selectedTab && selectedTab.component}
      </Fragment>
    );
  }
}

Ответ 7

Это решается с помощью <Link /> из material-ui вместо непосредственного использования <Link /> или <NavLink /> из реактивного маршрутизатора. Пример того же можно найти в документации здесь.

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

Кроме того, тег <Button /> имеет пропеллер для достижения этой цели

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

Обширное обсуждение этого можно найти здесь

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

Ответ 9

Другим простым способом является поддержка элемента Tab.

 <Tab label="My label" route="/go-here" onActive={handleActive}  />

Тогда handleActive выглядит как

handleActive(tab) {
     /*accessing your route from*/ tab.props.route
}

этот способ документирован на странице компонентов в качестве третьего примера функцииActive