I am having trouble finding an elegant solution for marking a tab as selected if the tabs are 'router managed' rather than through onChange(). The main issue is that the selected tab can be set on the wrapping tabs component only and not on the actual child tabs.
To provide some context, a common DRY approach for setting up location-aware navigations (i.e. navigation items that apply some styling when selected) with React Router looks as follows:
class NavList extends React.Component {
render() {
return (
<List>
<ListItemLink to="/one" primary="One"/>
<ListItemLink to="/two" primary="Two"/>
<ListItemLink to="/three" primary="Three"/>
</List>
);
};
}
const ListItemLink = ({to, primary, ...other}) => (
<Route path={to} children={({match}) => (
<ListItem button component={Link} to={to} {...other}>
<ListItemText primary={primary} classes={{text: match ? classes.active : ''}}/>
</ListItem>
)}/>
);
Here, the list item is wrapped by a Route component that tries to match the current location against its path prop and adjusts the props of the list item accordingly.
I would love to achieve the same thing with tabs but do not how. Hence, my thought of adding a selected prop to the Tab component:
class NavTabs extends React.Component {
render() {
return (
<Tabs>
<TabLink to="/one" label="One"/>
<TabLink to="/two" label="Two"/>
<TabLink to="/three" label="Three"/>
</Tabs>
);
};
}
const TabLink = ({to, label, ...other}) => (
<Route path={to} children={({match}) => (
<Tab label={label} value={to} component={Link} to={to} selected={match} {...other}>
)}/>
);
I realize that it is cleaner to have the selection state in the Tabs component but as explained, I have yet to figure out a way to handle this common case.
Ideas? Thoughts?
@jschwertfeger I like the idea but I fear it will require too much effort to support it. I think that we will be better off keeping thing simple. Let us know if you find a way to handle the issue with react-router.
@oliviertassinari That is too bad. The dirty workaround I use for now is to set value of Tabs to be the current location pathname:
class NavTabs extends React.Component {
const {location} = this.props;
render() {
return (
<Tabs value={location.pathname}>
<TabLink to="/one" label="One"/>
<TabLink to="/two" label="Two"/>
<TabLink to="/three" label="Three"/>
</Tabs>
);
};
}
const TabLink = ({to, label, ...other}) => (
<Route path={to} children={({match}) => (
<Tab label={label} value={to} component={Link} to={to} {...other}>
)}/>
);
This makes the crucial assumption that location.pathname always corresponds to one of the values defined in the tabs. I.e. the location when the tabs are being rendered has to be _/one_, _/two_ or _/three_ exactly, otherwise the Tabs component will raise a _the value provided ... is invalid_ error.
This also means that tabs can only be used on the lowest level of a hierarchical navigation. E.g. you could not have a tab point to _/customers_ and then have that tab marked active when displaying a child component/path _/customers/123_.
I came up with a pretty nice solution whereby I wrap Tabs in a HOC that accepts an array of selectors (essentially React Router path matchers), with each selector pointing to a tab index (or tab value) that should be selected in case the selector matches the current location. See https://gist.github.com/jschwertfeger/85c16c99f1abe7be5fe956046148647e
@jschwertfeger Thanks for sharing 馃憤 . I have been implementing something similar for the router of Next.js.
Most helpful comment
I came up with a pretty nice solution whereby I wrap
Tabsin a HOC that accepts an array of selectors (essentially React Router path matchers), with each selector pointing to a tab index (or tab value) that should be selected in case the selector matches the current location. See https://gist.github.com/jschwertfeger/85c16c99f1abe7be5fe956046148647e