Nested MaterialUI Tabs throws an error when opening second tabs level - javascript

I am trying to build a nested horizontal tabs in MaterialUI, I mean, a first tabs level that, when you click on it, open a second tabs level.
Here is a link to the working replicable code example: https://codesandbox.io/s/sweet-pasteur-x4m8z?file=/src/App.js
The problem is: When I click on first level, second level is opened, when I click on a item from second level, I get this error
Material-UI: the value provided to the Tabs component is invalid.
None of the Tabs' children match with "value21".
You can provide one of the following values: value11
For replicate the error, you could do next steps:
Click in "Label 1"
Click in "Label 1.1"
Error is thrown
I do not understand why that error, if I am splitting values of each tab in different states and, supposedly, it is all Ok. Maybe the way I use for implementing the nested tab is wrong, any idea what could be happening?
Thank you.

I created three tab components, one parent and two children. Then I Imported the children tab components into the parent. You can use vertical tabs for the child tab components to help with the layout. check out the how the parent tab looks like. Note all these are tab components
// Parent tab component
import React from 'react';
import PropTypes from 'prop-types';
import SwipeableViews from 'react-swipeable-views';
import { makeStyles, useTheme } from '#material-ui/core/styles';
import AppBar from '#material-ui/core/AppBar';
import Tabs from '#material-ui/core/Tabs';
import Tab from '#material-ui/core/Tab';
import Typography from '#material-ui/core/Typography';
import Box from '#material-ui/core/Box';
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`full-width-tabpanel-${index}`}
aria-labelledby={`full-width-tab-${index}`}
{...other}
>
{value === index && (
<Box p={3}>
<Typography>{children}</Typography>
</Box>
)}
</div>
);
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.any.isRequired,
value: PropTypes.any.isRequired,
};
function a11yProps(index) {
return {
id: `full-width-tab-${index}`,
'aria-controls': `full-width-tabpanel-${index}`,
};
}
const useStyles = makeStyles((theme) => ({
root: {
backgroundColor: theme.palette.background.paper,
width: 500,
},
}));
export default function FullWidthTabs() {
const classes = useStyles();
const theme = useTheme();
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
const handleChangeIndex = (index) => {
setValue(index);
};
return (
<div className={classes.root}>
<AppBar position="static" color="default">
<Tabs
value={value}
onChange={handleChange}
indicatorColor="primary"
textColor="primary"
variant="fullWidth"
aria-label="full width tabs example"
>
<Tab label="Item One" {...a11yProps(0)} />
<Tab label="Item Two" {...a11yProps(1)} />
</Tabs>
</AppBar>
<SwipeableViews
axis={theme.direction === 'rtl' ? 'x-reverse' : 'x'}
index={value}
onChangeIndex={handleChangeIndex}
>
<TabPanel value={value} index={0} dir={theme.direction}>
<ChildTabOne/>
</TabPanel>
<TabPanel value={value} index={1} dir={theme.direction}>
<ChildTabTwo/>
</TabPanel>
</SwipeableViews>
</div>
);
}

Related

Warning: Each child in a list should have a unique "key" prop. how to fix this?

Ive been using this project with out a problem and now all of a sudden I keep getting this error and it won't show my notes when I click on the my notes section. What do I have to do for it to go away. The backend is up and running and I can see the static data but it wont show on the app
import { makeStyles } from '#mui/styles'
import React from 'react'
import { Drawer } from '#mui/material'
import { Typography } from '#mui/material'
import List from '#mui/material/List'
import ListItem from '#mui/material/ListItem'
import ListItemIcon from '#mui/material/ListItemIcon'
import ListItemText from '#mui/material/ListItemText'
import { AddCircleOutlineOutlined, SubjectOutlined } from '#mui/icons-material'
import { useHistory, useLocation } from 'react-router-dom'
import AppBar from '#mui/material/AppBar'
import Toolbar from '#mui/material/Toolbar'
import { format } from 'date-fns'
import { red } from '#mui/material/colors'
const drawerWidth = 240 // 500 - subtract this number from
const useStyles = makeStyles((theme) => {
return{
page: {
background: '#E5E4E2',
width: '100%',
padding: theme.spacing(3)
},
drawer: {
width: drawerWidth
},
drawerPaper: {
width: drawerWidth
},
root: {
display: 'flex' //places the drawer side by side with the page content
},
active: {
background: '#E5E4E2'
},
// title:{
// padding: theme.spacing(13),
// alignItems: 'center'
// },
}})
export default function Layout({ children }) {
const classes = useStyles()
const history = useHistory()
const location = useLocation()
const menuItems = [
{
text: 'My Projects',
icon: <SubjectOutlined color="secondary" />,
path: '/'
},
{
text: 'Create Project',
icon: <AddCircleOutlineOutlined color="secondary" />,
path: '/create'
}
]
return (
<div className={classes.root}>
{/* side drawer */}
<Drawer
className={classes.drawer}
variant='permanent' //Lets MUI know we want it on the page permanently
anchor="left" // position of drawer
classes={{ paper: classes.drawerPaper}}
>
<div>
<Typography variant="h5" sx={{textAlign: 'center'}}>
Projects
</Typography>
</div>
{/* List / Links */}
<List>
{menuItems.map(item => (
<div className={location.pathname == item.path ? classes.active : null}>
<ListItem key={item.text} button onClick={() => history.push(item.path)}>
<ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={item.text} />
</ListItem>
</div>
))}
</List>
</Drawer>
<div className={classes.page}>
<div className={classes.toolbar}></div>
{children}
</div>
</div>
)
}
enter image description here
Updated
I'm sorry, of course, you should just move key to the parent div. I didn't notice it. Chris who answered in the comments is right and my answer was not needed. I rewrote the answer.
To have an unique key use index in map or like you did item.text if text is unique for each element in map.
menuItems.map((item,index) =>
The idea is that map has to contain unique key for each element.
In result we have:
<div key={item.text} className={location.pathname == item.path ? classes.active : null}>
or
<div key={index} className={location.pathname == item.path ? classes.active : null}>
And you need to remove key from the List.
Hope this helps! Regards,

How to conditionally render options in Material UI list?

I've been trying to conditionally render the list items based on the user's selection but i can't seem to figure it out. I'd like to guide the users through steps in order to arrive at a final solution. I even tried rendering a different menu but it looks like the component does not re-render once the option selected change so the menu stays the same.
import React, { Fragment, useState, useEffect } from 'react';
import Typography from '#material-ui/core/Typography';
import Grid from '#material-ui/core/Grid';
import TextField from '#material-ui/core/TextField';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Checkbox from '#material-ui/core/Checkbox';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import ListItemText from '#material-ui/core/ListItemText';
import Menu from '#material-ui/core/Menu';
import MenuItem from '#material-ui/core/MenuItem';
function TroubleshootingForm() {
const [anchorEl, setAnchorEl] = useState(null);
const [softorHardProb, setSoftorHardProb] = useState(null);
const [selectedIndex, setSelectedIndex] = useState(1);
const options = ['Select issue type', 'Software', 'Hardware'];
const softwareOptions = ['Smart Assistant', 'Sounds', '3rd Party Apps', 'OPPO Clone Phone'];
const hardwareOptions = ['Dislay', 'Battery', 'Charger', 'DOA'];
const handleClickListItem = event => {
setAnchorEl(event.currentTarget);
};
const handleMenuItemClick = (event, index) => {
setSelectedIndex(index);
setAnchorEl(null);
console.log(selectedIndex);
};
const handleClose = () => {
setAnchorEl(null);
};
// useEffect(() => {}, []);
return (
<Fragment>
<Typography variant="h6" gutterBottom>
Hardware or Software?
</Typography>
<Grid container spacing={3}>
<Grid item xs={12}>
<div>
<List component="nav" aria-label="Device settings">
<ListItem
button
aria-haspopup="true"
aria-controls="lock-menu"
aria-label="when device is locked"
onClick={handleClickListItem}
>
<ListItemText
primary="When device is locked"
secondary={
selectedIndex === 1
? options[selectedIndex]
: softwareOptions[
(
<ListItemText
primary="When device is locked"
secondary={
selectedIndex === 1
? options[selectedIndex]
: softwareOptions[selectedIndex]
}
/>
)
]
}
/>
</ListItem>
</List>
<Menu
id="lock-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
{options.map((option, index) => (
<MenuItem
key={option}
disabled={index === 0}
selected={index === selectedIndex}
onClick={event => handleMenuItemClick(event, index)}
>
{option}
</MenuItem>
))}
</Menu>
</div>
</Grid>
{/* <Grid item xs={12}>
{renderNextstep()}
</Grid> */}
</Grid>
</Fragment>
);
}
export default TroubleshootingForm;
The idea is for the user to chose options and the render the next step with other options and then so on until the final solution based on the previous selection. I added a screenshot from another app and the idea is that the current rendering of the component changes based on the selection. It starts by showing step 1 > select an option and component re-renders showing step 2 etc...

Why do Material UI tabs stop working when I use a .map to populate the content dynamically instead of hard coding?

I have implemented Material UI's tabs successfully by hard-coding the content, but when I tried to make a my hard coded tabs with a .map function to populate the content from a data source (simple json), it no longer works. Can anyone see why? The only change I made was to the MyTabs component below where there are now two .map functions instead of hard coded tabs.
Many thanks for your help!
Here is my data:
export const TabsData = [
{
tabTitle: 'Tab 1',
tabContent: 'Hello 1',
},
{
tabTitle: 'Tab 2',
tabContent: 'Hello 2',
},
{
tabTitle: 'Tab 3',
tabContent: 'Hello 3',
},
];
Here is my MyTabs component:
import React, { useState } from 'react';
// Material UI
import Tabs from '#material-ui/core/Tabs';
import Tab from '#material-ui/core/Tab';
// Data
import { TabsData } from '../../page-templates/full-page-with-tabs/FullPageWithTabsData';
//Components
import TabContentPanel from '../tabs/tab-content-panel/TabContentPanel';
const MyTabs = () => {
const classes = useStyles();
const initialTabIndex = 0;
const [value, setValue] = useState(initialTabIndex);
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<>
<Tabs
value={value}
onChange={handleChange}
aria-label=""
className={classes.tabHeight}
classes={{ indicator: classes.indicator }}
>
{TabsData.map((tabInfo, index) => (
<>
<Tab
label={tabInfo.tabTitle}
id={`simple-tab-${index}`}
ariaControls={`simple-tabpanel-${index}`}
/>
</>
))}
</Tabs>
{TabsData.map((tabInfo, index) => (
<TabContentPanel value={value} index={index}>
{tabInfo.tabContent}
</TabContentPanel>
))}
</>
);
};
export default MyTabs;
And here is the TabsPanel component:
import React from 'react';
import PropTypes from 'prop-types';
// Material UI
import { Box } from '#material-ui/core';
function TabContentPanel(props) {
const { children, value, index, ...other } = props;
const classes = useStyles();
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && <Box className={classes.contentContainer}>{children}</Box>}
</div>
);
}
TabContentPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.any.isRequired,
value: PropTypes.any.isRequired,
};
export default TabContentPanel;
It doesn't work because you added some extra Fragments (<> and </>) in the Tabs component and the Tabs component doesn't accept a Fragment as a child:
If you remove those, it will work as expected:
{TabsData.map((tabInfo, index) => (
<Tab
label={tabInfo.tabTitle}
id={`simple-tab-${index}`}
key={tabInfo.tabTitle}
ariaControls={`simple-tabpanel-${index}`}
/>
))}
And please use the key prop with a unique id if you create an array of elements. Each child in a list should have a unique "key" prop.

How to customize the font size of an active tab in material-ui

My Requirement is such that only the active tab should have a specific color and font-size.
This is my code:
<Tabs
value={value}
onChange={handleChange}
classes={{indicator: classes.customStyleOnActiveTab}}
aria-label="some text"
>
<Tab label={<span className={classes.customStyleOnTab}>Tab 1</span>} />
<Tab label={<span className={classes.customStyleOnTab}> Tab 2</span>}/>
</Tabs>
However, it changes the styling on both the tabs whereas I want only the active tab to have a particular style (blue color and bold font). The other tab should have a normal font-weight and color:
If I use textColor property, I can achieve the desired result but I wouldn't be able to customize it. I dug into the material doc, and eventually tried all the CSS classes they've exposed, without any luck
For adding custom/different style to the active tab you can assign a class to the active tab conditionally. Every Tab has its own value (which is their index value also) and assign them class conditionally.
Here is the link of codesandbox:- https://codesandbox.io/s/elegant-tu-lxi1g?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
import {Tabs, Tab, makeStyles} from '#material-ui/core'
export default function App() {
const classes = useStyles()
const [value, setValue] = useState(0)
const handleChange = (e, newVal)=>{
setValue(newVal)
}
return (
<div className="App">
<Tabs
value={value}
onChange={handleChange}
classes={{indicator: classes.customStyleOnActiveTab}}
aria-label="some text"
>
<Tab label={<span className={ value === 0 ? classes.activeTab : classes.customStyleOnTab}>Tab 1</span>} />
<Tab label={<span className={ value === 1 ? classes.activeTab : classes.customStyleOnTab}> Tab 2</span>}/>
</Tabs>
</div>
);
}
const useStyles = makeStyles({
customStyleOnTab:{
fontSize:'15px',
color:'green'
},
customStyleOnActiveTab:{
color:'red'
},
activeTab:{
fontSize:'16px',
fontWeight:'600',
color:'pink'
}
})
also, you can assign the className to the Tab element instead of span for keeping the label prop clean, tidy, and uniform.
You can change by use makeStyle.
const useStyle = makeStyles((theme) => ({
tab: {
"& .Mui-selected": {
color: "red",
fontSize: "20px"
}
}
})
then
<Tabs
className={classes.tab}
>
<Tab label="Teacher Information" />
<Tab label="Contract" />
</Tabs>
Another possible solution is to use withStyles.
...
import { default as MuiTab } from "#material-ui/core/Tab";
import { withStyles } from "#material-ui/core/styles";
const styles = {
// check https://material-ui.com/api/tab/#css
// for more overridable styles
selected: {
fontSize: "16px",
fontWeight: "600",
color: "pink"
}
};
const Tab = withStyles(styles)((props) => <MuiTab {...props} />);
function App() {
const [value, setValue] = React.useState(0);
const handleChange = (e, newVal) => {
setValue(newVal);
};
return (
<div className="App">
<Tabs value={value} onChange={handleChange}>
<Tab label="Tab 1" />
<Tab label="Tab 2" />
</Tabs>
</div>
);
}

Material-ui adding Link component from react-router

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:
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.
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.
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>
You can try this simple method
<Tab label='Most popular ideas' to='/myPath' component={Link} />
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
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'} />▾</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))
TypeScript implementation of the router-driven tabs.
For those who look for the TypeScript implementation. Easy configurable. Driven by tabs configuration.
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>
);
}
}
Here's another implementation of React with hooks, Material-UI with tabs, React Router with Link, and TypeScript.
import * as React from "react";
import { BrowserRouter as Router, Route, Redirect, Switch, Link, LinkProps } from 'react-router-dom';
import AppBar from '#material-ui/core/AppBar';
import Tabs from '#material-ui/core/Tabs';
import { default as Tab, TabProps } from '#material-ui/core/Tab';
import Home from './Home';
import ProductManagement from './ProductManagement';
import Development from './Development';
import HomeIcon from '#material-ui/icons/Home';
import CodeIcon from '#material-ui/icons/Code';
import TimelineIcon from '#material-ui/icons/Timeline';
const LinkTab: React.ComponentType<TabProps & LinkProps> = Tab as React.ComponentType<TabProps & LinkProps>;
function NavBar() {
const [value, setValue] = React.useState(0);
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
setValue(newValue);
};
return (
<div >
<AppBar position="static" >
<Tabs value={value} onChange={handleChange} centered>
<LinkTab label='Home' icon={ <HomeIcon />} component={Link} to="/" />
<LinkTab label='Development' icon={<CodeIcon />} component={Link} to="/dev" />
<LinkTab label='Product Management' icon={<TimelineIcon />} component={Link} to="/pm" />
</Tabs>
</AppBar>
</div>
)
};
export default function App() {
return (
<Router>
<div>
<NavBar />
<Switch>
<Route exact path="/" component={ Home } />
<Route exact path="/dev" component={ Development } />
<Route exact path="/pm" component={ ProductManagement } />
<Redirect from="/" to="/" />
</Switch>
</div>
</Router>
)
}
So my work-around for this solution has been quite reliable, though it may be more manual of a solution than what you're looking to do.
The strategy that I've been using is to actually not even use the Link Component. Instead, you'll utilize the Tabs onChange property as a callback that can respond to Tab clicks, and track location manually with Props on the Parent.
You can import a utility called History from react-router that will allow you to manually push locations. While using React-Router, your component tree will have access to Location prop that has a pathname key with the string of your current location.
We will manually parse this string into the components that make up your current URL, then use a Switch statement to decide both which tab is currently selected and also where to link to when a tab is clicked. (This gives you a fair amount of control over navigation)
( e.g. ['', 'latest'] )
Here is a mock up of what your component MAY look like after integrating this solution.
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>
);
}
});
Check this link, I implemented the solution and worked for me
Composition in material UI
If you use NextJs, you can do it like that, and create your own component.
*i didn`t wrap the Tab with 'a' tag, because it added automatically
const WrapTab = (props) => {
const { href } = props
return (
<Link href={href} style={{ width: "100%" }}>
<Tab {...props} />
</Link>
)}
and then this is your return
<Tabs
value={value}
indicatorColor="primary"
textColor="primary"
onChange={handleChange}
variant="fullWidth"
>
<WrapTab href="/testPage/?tab=0" icon={<MenuIcon />} />
<WrapTab href="/testPage/?tab=1" icon={<StampIcon2 />} />
<WrapTab href="/testPage/?tab=2" icon={<ShopIcon />} />
<WrapTab href="/testPage/?tab=3" icon={<PenIcon />} />
<WrapTab href="/testPage/?tab=4" icon={<ProfileIcon />} />
</Tabs>
link to material-ui docs:
https://material-ui.com/guides/composition/
This seems to work for me
import { Link as RouterLink } from 'react-router-dom';
import Link from '#mui/material/Link';
<Link to={menuItem.url} component={RouterLink} aria-current="page">
{menuItem.label}
</Link>
For anyone looking to wrap Material-ui Link with Next.js Link
import Link from "next/link"
import MuiLink from "#material-ui/core/Link"
const CustomNextLink = ({href, alt}) => ({children, ...rest}) => (
<Link href={href} alt={alt}>
<MuiLink {...rest}>
{children}
</MuiLink>
</Link>)
Then pass it to you Tab component
<Tab
key={...}
label={title}
icon={icon}
component={CustomNextLink({href: to, alt: title})}
style={...}
className={...}
classes={{selected: ...}}
{...a11yProps(index)}
/>
Use the href="" option as shown below:
<Tab
href="/en/getting-started"
label="Contact US"
style={{ color: "white", textDecoration: "none" }}
/>
To remove the ripple effect on clicking, use the option disableRipple

Categories

Resources