保持Material UI标签和React Router同步

时间:2017-05-12 22:12:14

标签: reactjs react-router material-ui

是否有一种非hacky方式来保持Material UI标签和React路由器同步?

基本上,我想在用户点击标签[1]时更改网址,当用户使用非标签链接或按钮导航到其他网页时,标签应自动更改,当然还有直接访问权限[2]和页面刷新。

此外,反应路由器的非精确功能也很不错,因此/foo选项卡应对/foo/foo/bar/1都有效

[1]其他SO答案建议直接使用历史api,这是反应路由器的一个好习惯吗?

[2]我不确定它叫什么,我的意思是当用户直接加载例如/foo而不是加载/然后通过标签导航到/foo或链路

修改

我创建了一个包装组件来完成这项工作,但有一些问题:

class CustomTabs extends React.PureComponent {
    constructor() {
        super();

        this.state = {
            activeTab: 0
        }
    }

    setActiveTab(id) {
        this.setState({
            activeTab: id
        });
        return null;
    }

    render() {
        return (
            <div>
                {this.props.children.map((tab,index) => {
                    return (
                        <Route
                            key={index}
                            path={tab.props.path||"/"}
                            exact={tab.props.exact||false}
                            render={() => this.setActiveTab(index)}
                        />
                    );
                })}
                <Tabs
                    style={{height: '64px'}}
                    contentContainerStyle={{height: '100%'}}
                    tabItemContainerStyle={{height: '100%'}}
                    value={this.state.activeTab}
                >
                    {this.props.children.map((tab,index) => {
                        return (
                            <Tab
                                key={index}
                                value={index}
                                label={tab.props.label||""}
                                style={{paddingLeft: '10px', paddingRight: '10px', height: '64px'}}
                                onActive={() => {
                                    this.props.history.push(tab.props.path||"/")
                                }}
                            />
                        );
                    })}
                </Tabs>
            </div>
        );
    }
}

我正在使用它:

<AppBar title="Title" showMenuIconButton={false}>
    <CustomTabs history={this.props.history}>
        <Tab label="Home" path="/" exact/>
        <Tab label="Foo" path="/foo"/>
        <Tab label="Bar" path="/bar"/>
    </CustomTabs>
</AppBar>

可是:

  • 我在控制台收到此警告:
  

警告:setState(...):无法在现有状态转换期间更新(例如在render或其他组件的构造函数中)。渲染方法应该是道具和状态的纯函数;构造函数副作用是反模式,但可以移动到componentWillMount

我认为这是因为我在调用render()后立即设置状态 - 因为Route.render,但我不知道如何解决这个问题。

编辑#2

我终于解决了所有问题,但有点笨拙。

class CustomTabsImpl extends PureComponent {
    constructor() {
        super();

        this.state = {
            activeTab: 0
        }
    }

    componentWillMount() {
        this.state.activeTab = this.pathToTab(); // eslint-disable-line react/no-direct-mutation-state
    }

    componentWillUpdate() {
        setTimeout(() => {
            let newTab = this.pathToTab();
            this.setState({
                activeTab: newTab
            });
        }, 1);
    }

    pathToTab() {
        let newTab = 0;

        this.props.children.forEach((tab,index) => {
            let match = matchPath(this.props.location.pathname, {
                path: tab.props.path || "/",
                exact: tab.props.exact || false
            });
            if(match) {
                newTab = index;
            }
        });

        return newTab;
    }

    changeHandler(id, event, tab) {
        this.props.history.push(tab.props['data-path'] || "/");
        this.setState({
            activeTab: id
        });
    }

    render() {
        return (
            <div>
                <Tabs
                    style={{height: '64px'}}
                    contentContainerStyle={{height: '100%'}}
                    tabItemContainerStyle={{height: '100%'}}
                    onChange={(id,event,tab) => this.changeHandler(id,event,tab)}
                    value={this.state.activeTab}
                >
                    {this.props.children.map((tab,index) => {
                        return (
                            <Tab
                                key={index}
                                value={index}
                                label={tab.props.label||""}
                                data-path={tab.props.path||"/"}
                                style={{height: '64px', width: '100px'}}
                            />
                        );
                    })}
                </Tabs>
            </div>
        );
    }
}

const CustomTabs = withRouter(CustomTabsImpl);

2 个答案:

答案 0 :(得分:1)

首先,感谢您回答您的问题。 我以不同的方式处理这个问题,我决定在这里发帖,以表达对社区的赞赏。

我的理由是:&#34;如果我能告诉 Tab 而不是 Tabs 组件关于哪一个是活动的,那会更简单。&#34;

完成这一点非常简单,可以通过为 Tabs 组件设置一个已知的固定值并将该值分配给应该处于活动状态的任何选项卡来实现。

此解决方案要求托管选项卡的组件可以访问诸如来自react-router的位置和匹配之类的道具,如下所示

首先,我们创建一个函数,该工厂从render方法中删除膨胀代码。如果所需的路线匹配,以下是将固定的Tabs值设置为Tab,否则我只是抛出任意常量,例如Infinity。

&#13;
&#13;
const mountTabValueFactory = (location, tabId) => (route) => !!matchPath(location.pathname, { path: route, exact: true }) ? tabId : Infinity;
&#13;
&#13;
&#13;

之后,您只需将信息插入渲染功能即可。

&#13;
&#13;
render() {
   const {location, match} = this.props;
   const tabId = 'myTabId';
   const getTabValue = mountTabValueFactory(location, tabId);

   return (
     <Tabs value={tabId}>
       <Tab
         value={getTabValue('/route/:id')}
         label="tab1"
         onClick={() => history.push(`${match.url}`)}/>
       <Tab
         value={getTabValue('/route/:id/sub-route')}
         label="tab2"
         onClick={() => history.push(`${match.url}/sub-route`)}
       />
     </Tabs>
   )
}
&#13;
&#13;
&#13;

答案 1 :(得分:-2)

您可以使用反应路由器NavLink组件

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

<NavLink
  activeClassName="active"
  to="/foo"
>Tab 1</NavLink>

当/ foo是路由时,active类将被添加到此链接。 NavLink还有一个isActive道具,可以传递一个函数来进一步自定义确定链接是否有效的功能。

https://reacttraining.com/react-router/web/api/NavLink