Render components on Material UI tabs - reactjs

I have been trying to render certain components on clicking inside a tab content.
This is what i have been doing:
import React, { Component } from "react";
export class Global extends Component {
render() {
return <div>Global</div>;
}
}
export default Global;
This is a basic component to show.
import React, { Component } from "react";
import PropTypes from "prop-types";
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 Toolbar from "#material-ui/core/Toolbar";
import Button from "#material-ui/core/Button";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import IconButton from "#material-ui/core/IconButton";
import SearchIcon from "#material-ui/icons/Search";
import Link from "#material-ui/core/Link";
import Global from "../Dashboard/Global";
import Graficos from "../Dashboard/Graficos";
import Individual from "../Dashboard/Individual";
import Temporais from "../Dashboard/Temporais";
import CSSTransitionGroup from "react-addons-css-transition-group";
import { Paper } from "#material-ui/core";
import { classes } from "../constants/tabs";
function TabContainer(props) {
return (
<Typography
component="div"
style={{
padding: 8 * 3
}}
>
{" "}
{props.children}{" "}
</Typography>
);
}
TabContainer.propTypes = {
children: PropTypes.node.isRequired
};
//const sections = ["Indicador 1", "Indicador 2", "Indicador 2", "Indicador 2"];
const PageShell = (Page, previous) => {
return props => (
<div className="page">
<CSSTransitionGroup
transitionAppear={true}
transitionAppearTimeout={600}
transitionEnterTimeout={600}
transitionLeaveTimeout={600}
transitionName={props.match.path === "/one" ? "SlideIn" : "SlideOut"}
>
{console.log(props)}
<Page {...props} />
</CSSTransitionGroup>
</div>
);
};
function ItemOne(theme) {
return (
<Paper>
<div>Item 1</div>
</Paper>
);
}
function ItemTwo(theme) {
return (
<Paper>
<div>Item two</div>
</Paper>
);
}
export default function Header() {
const [value, setValue] = React.useState(0);
function handleChange(event, newValue) {
setValue(newValue);
}
return (
<div className={classes.root}>
<AppBar position="static">
<Tabs value={value} onChange={handleChange}>
{/* <Tabs value={value} onChange={handleChange}>
{sections.map(section => (
<Tab label={section} />
))}
</Tabs> */}
<Tab label="Item One" component={Link} to="/global" />
<Tab label="Item Two" component={Link} to="/individual" />
<Tab label="Item Three" component={Link} to="/temporal" />
<Tab label="Item Four" component={Link} to="/graficos" />
</Tabs>
</AppBar>
<Switch>
<Route path="/global" component={PageShell(ItemOne)} />
<Route path="/individual" component={PageShell(ItemTwo)} />
</Switch>
{/* {value === 0 && <TabContainer>Item One</TabContainer>}
{value === 1 && <TabContainer>Item Two</TabContainer>}
{value === 2 && <TabContainer>Item Three</TabContainer>}
{value === 3 && <TabContainer>Item Four</TabContainer>} */}
</div>
);
}
This is my material header and im trying to load that global component below my header. The idea is that my header is fixed and the component renders on tab click. Can anyone help me with what im doing wrong ?

Related

signOut being passed as props from one component to App.js, but on click on logout icon doesn't allow to logout from system

In my react hooks signOut is being passed as props from DashboardNav.js component to App.js. On click on logout icon in App.js is not actually logging out from the system. I am actually using gapi-scirpt for performing the logout operation, could someone please advise me how can i achieve this ?
CodeSandBox link:
https://codesandbox.io/s/fervent-ioana-pz5jyz?file=/src/dashboardNav.js:0-1199
// App.js
import React, { useEffect, useState } from "react";
import DashboardNavbar from "./dashboardNav";
import { gapi } from "gapi-script";
export default function App() {
const [isMobileNavOpen, setMobileNavOpen] = useState(false);
const { signOut } = () => {
alert("hello");
const auth2 = gapi.auth2.getAuthInstance();
if (auth2 != null) {
auth2.signOut().then(
auth2.disconnect().then(console.log("LOGOUT SUCCESSFUL")),
localStorage.removeItem("loginEmail"),
localStorage.removeItem("userImage"),
//history.push("/"),
console.log("Logged out successfully !")
);
}
};
return (
<div className="App">
<DashboardNavbar
logout={signOut}
onMobileNavOpen={() => setMobileNavOpen(true)}
/>
</div>
);
}
// dashboardNav.js
import { useState } from "react";
import { Link as RouterLink } from "react-router-dom";
import PropTypes from "prop-types";
import {
AppBar,
Badge,
Box,
Hidden,
IconButton,
Toolbar
} from "#material-ui/core";
import MenuIcon from "#material-ui/icons/Menu";
import NotificationsIcon from "#material-ui/icons/NotificationsOutlined";
import InputIcon from "#material-ui/icons/Input";
const DashboardNavbar = ({ onMobileNavOpen, signOut, ...rest }) => {
const [notifications] = useState([]);
return (
<AppBar elevation={0} {...rest} style={{ background: "#1976D2" }}>
<Toolbar>
<RouterLink to="/">
<img alt="Logo" src="images/simpro.PNG" width="80px" />
</RouterLink>
<Box sx={{ flexGrow: 1 }} />
<Hidden mdDown>
<IconButton color="inherit" onClick={signOut}>
<InputIcon />
</IconButton>
</Hidden>
<Hidden lgUp>
<IconButton color="inherit" onClick={onMobileNavOpen}>
<MenuIcon />
</IconButton>
</Hidden>
</Toolbar>
</AppBar>
);
};
DashboardNavbar.propTypes = {
onMobileNavOpen: PropTypes.func
};
export default DashboardNavbar;

React Class Component not working properly for me

I have a component of "Drawer", I am opening and closing this drawer with component state and passing this state down to the Drawer Component and Also passing a callback function that can help me to close the drawer,
Now the issue is that when ever I am trying to open that drawer, the whole ui is disappearing. Need help
here is the code
import IconButton from '#material-ui/core/IconButton';
import MenuIcon from '#material-ui/icons/Menu';
import Drawer from '../Drawer';
import Header from '../Header';
export default class LandingPage extends Component {
constructor() {
super();
this.state = {
openDrawer: false,
};
}
handleDrawer = (state) => {
this.setState({
openDrawer: state,
});
};
render() {
return (
<div className='landing-page-container'>
<div className='menu-btn'>
<IconButton
edge='start'
color='inherit'
aria-label='menu'
onClick={() => this.handleDrawer(true)}
>
<MenuIcon />
</IconButton>
</div>
<Drawer
handleDrawer={this.handleDrawer}
openDrawer={this.state.openDrawer}
/>
</div>
);
}
}
Here is Drawer
import React, { Component } from 'react';
import {
Drawer,
IconButton,
Divider,
List,
ListItem,
ListItemIcon,
Button,
ListItemText,
makeStyles,
} from '#material-ui/core';
import { withStyles } from '#material-ui/core/styles';
import {
ChevronLeft,
NoteAdd,
Person,
PersonAdd,
AttachMoney,
Build,
Settings,
} from '#material-ui/icons';
const styles = makeStyles((theme) => ({
root: {
display: 'flex',
},
drawerPaper: {
backgroundColor: theme.primary,
},
}));
export class DrawerMenu extends Component {
render() {
const { classes, openDrawer, handleDrawer } = this.props;
return (
<div className='drawer-container'>
<Drawer
variant='persistent'
anchor='left'
open={false}
classes={{
paper: classes.drawerPaper,
}}
backgroundColor='primary'
>
<div className='logo-icon'>
<div className='logo'>Logo</div>
<div className='collapse-icon'>
<IconButton onClick={() => handleDrawer(false)}>
<ChevronLeft />
</IconButton>
</div>
</div>
<Divider />
<div className='drawer-menu-container'>
<div className='drawer-menu'>
<List>
{[
'Booking',
'Positions',
'User Management',
'Trading',
'Instruments',
].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>
{index === 0 && <NoteAdd />}
{index === 1 && <Person />}
{index === 2 && <PersonAdd />}
{index === 3 && <AttachMoney />}
{index === 4 && <Build />}
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
</div>
<div className='drawer-action-button'>
<div className='setting-logout'>
<div className='setting'>
<Settings />
</div>
<div className='logout'>
<Button variant='contained' color='primary'>
Logout
</Button>
</div>
</div>
</div>
</div>
</Drawer>
</div>
);
}
}
export default withStyles(styles, { withTheme: true })(DrawerMenu);
Your problem could be related to the fact that your Drawer is not on a Fragment. Try to modify your Drawer component code like:
import React, { Component, Fragment } from 'react'; //<-- import Fragment
...
export class DrawerMenu extends Component {
render() {
<div className='drawer-container'>
<Fragment>
<Drawer
variant='persistent'
anchor='left'
open={false}
classes={{
paper: classes.drawerPaper,
}}
backgroundColor='primary'
>
...
</Drawer>
</Fragment>
</div>
};
}
This should solve your problem.

Menu list overlapping with another Menu list

I'm new to react.js and material UI, i'm try to make a Navigation top navbar for my project,
i use the 'MenuList' composition to my top navbar, the first one Button is success,but when i add another one and click it, it will overlapping with first menu list. Can someone give some hints? Thank you all.
here is the problem image
and here is my navbar source code
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Paper from '#material-ui/core/Paper';
import Tabs from '#material-ui/core/Tabs';
import Tab from '#material-ui/core/Tab';
import ClickAwayListener from '#material-ui/core/ClickAwayListener';
import Button from '#material-ui/core/Button';
import Grow from '#material-ui/core/Grow';
import Popper from '#material-ui/core/Popper';
import MenuItem from '#material-ui/core/MenuItem';
import MenuList from '#material-ui/core/MenuList';
import { Link, Route, withRouter } from 'react-router-dom';
const useStyles = makeStyles({
root: {
flexGrow: 1,
},
});
export default function CenteredTabs() {
const classes = useStyles();
const [value, setValue] = React.useState(0);
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef(null);
const handleChange = (event, newValue) => {
setValue(newValue);
};
const handleToggle = () => {
setOpen(prevOpen => !prevOpen);
};
const handleClose = event => {
if (anchorRef.current && anchorRef.current.contains(event.target)) {
return;
}
setOpen(false);
};
function handleListKeyDown(event) {
if (event.key === 'Tab') {
event.preventDefault();
setOpen(false);
}
}
// return focus to the button when we transitioned from !open -> open
const prevOpen = React.useRef(open);
React.useEffect(() => {
if (prevOpen.current === true && open === false) {
anchorRef.current.focus();
}
prevOpen.current = open;
}, [open]);
return (
<Paper className={classes.root}>
<Tabs
value={value}
onChange={handleChange}
indicatorColor="secondary"
textColor="primary"
>
<Tab label="WeniPay" to="/" component={Link} style={{ float: "left" }} />
<Tab label="Home" to="/" component={Link} />
<Tab label="Login" to="/works" component={Link} />
<Tab label="Pay" to="/payPage" component={Link} />
</Tabs>
<Button
ref={anchorRef}
aria-controls={open ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={handleToggle}
>
Toggle Menu Grow
</Button>
<Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}
>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList autoFocusItem={open} id="menu-list-grow" onKeyDown={handleListKeyDown}>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
<Button
ref={anchorRef}
aria-controls={open ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={handleToggle}
>
s
</Button>
<Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}
>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList autoFocusItem={open} id="menu-list-grow" onKeyDown={handleListKeyDown}>
<MenuItem onClick={handleClose}>1</MenuItem>
<MenuItem onClick={handleClose}>2</MenuItem>
<MenuItem onClick={handleClose}>3</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</Paper>
);
}
and here is main page
import React from 'react';
import styles from './App.module.scss';
import { Link, Route, withRouter } from 'react-router-dom';
import HomePage from './HomePage';
import WorkPage from './WorkPage';
import WorkPageDetail from './WorkPageDetail';
import Header from './header';
import PayPage from './PayPage';
class App extends React.Component {
render() {
const { location } = this.props;
return (
<div className={styles.App}>
{/* header */}
<header className={styles.header}>
<div className={styles.box}>
<Header />
</div>
</header>
{/* content */}
<section className={styles.content}>
<Route path="/" exact component={HomePage} />
<Route path="/works" exact component={WorkPage} />
<Route path="/works/:id" exact component={WorkPageDetail} />
<Route path="/payPage" exact component={PayPage} />
</section>
{/* footer */}
<footer className={styles.footer}>
<p>© <b>MyPay</b></p>
</footer>
</div>
);
}
}
export default withRouter(App);
You can make archorEl as array so you can set like
// set anchor as empty list so we can track it later
const [anchorEl, setAnchorEl] = React.useState<[] | HTMLElement[]>([]));
const openMenu = (event, index) => {
const tempAnchor = anchorEl;
tempAnchor[index] = event.currentTarget; // only set change version
setAnchorEl(tempAnchor);
};
const closeMenu = () => {
setAnchorEl([]); // now we set it to empty list
}
and our tsx would be like
<Menu
id={row.id}
anchorEl={anchorEl[index]}
keepMounted
open={Boolean(anchorEl[index])}
onClose={() => closeMenu()}
>
<MenuItem> Foo</MenuItem>
<MenuItem> Bar</MenuItem>
</Menu>

React Changing the state

I have an component with tabs that have an state. By default, it should open the first tab, which is the state value = 0. But, when i import that component in another component, it should open the assigned state, like state value = 1. Please check the below example. How can i achieve that?
Thanks in advance
/**Component One***/
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } 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';
function TabContainer(props) {
return (
<Typography component="div" style={{ padding: 8 * 3 }}>
{props.children}
</Typography>
);
}
TabContainer.propTypes = {
children: PropTypes.node.isRequired,
};
const styles = theme => ({
root: {
flexGrow: 1,
backgroundColor: theme.palette.background.paper,
},
});
class SimpleTabs extends React.Component {
state = {
value: 0,
};
handleChange = (event, value) => {
this.setState({ value });
};
render() {
const { classes } = this.props;
const { value } = this.state;
return (
<div className={classes.root}>
<AppBar position="static">
<Tabs value={value} onChange={this.handleChange}>
<Tab label="Item One" />
<Tab label="Item Two" />
<Tab label="Item Three" />
</Tabs>
</AppBar>
{value === 0 && <TabContainer>Item One</TabContainer>}
{value === 1 && <TabContainer>Item Two</TabContainer>}
{value === 2 && <TabContainer>Item Three</TabContainer>}
</div>
);
}
}
SimpleTabs.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(SimpleTabs);
/**Component Two**/
import React from 'react';
import SimpleTabs from './SimpleTabs'
class ComponentTwo extends Component {
render () {
return (
<SimpleTabs value={1}/>
)
}
}
export default ComponentTwo
You are passing the props so you should check the props value then use the state value inside the component.
so the render function should be
import React from "react";
import PropTypes from "prop-types";
import { withStyles } 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";
function TabContainer(props) {
return (
<Typography component="div" style={{ padding: 8 * 3 }}>
{props.children}
</Typography>
);
}
TabContainer.propTypes = {
children: PropTypes.node.isRequired
};
const styles = theme => ({
root: {
flexGrow: 1,
backgroundColor: theme.palette.background.paper
}
});
class SimpleTabs extends React.Component {
state = {
Tabvalue: 1
};
componentDidMount() {
if (this.props.open) {
this.setState({ Tabvalue: this.props.open });
}
}
handleChange = (event, value) => {
this.setState({ Tabvalue: value });
};
render() {
const { classes } = this.props;
const { Tabvalue } = this.state;
let value = Tabvalue;
return (
<div className={classes.root}>
<AppBar position="static">
<Tabs value={value} onChange={this.handleChange}>
<Tab label="Item One" />
<Tab label="Item Two" />
<Tab label="Item Three" />
</Tabs>
</AppBar>
{value === 0 && <TabContainer>Item One</TabContainer>}
{value === 1 && <TabContainer>Item Two</TabContainer>}
{value === 2 && <TabContainer>Item Three</TabContainer>}
</div>
);
}
}
SimpleTabs.propTypes = {
classes: PropTypes.object.isRequired
};
export default withStyles(styles)(SimpleTabs);
/**Component Two**/
import React from 'react';
import SimpleTabs from './SimpleTabs'
class ComponentTwo extends React.Component {
render() {
return <SimpleTabs open={2} />;
}
}
export default ComponentTwo
Here, pass the open value as props, if by props value don't exist the default 1 tab will be opened. Don't use 0 as state, as the type-check put it as false value.
You can pass the tab value in props
state = {
value: props.tabValue,
};
<SimpleTabs value={tabvalue}/>

Material-UI's Tabs integration with react router 4?

The new react-router syntax uses the Link component to move around the routes. But how could this be integrated with material-ui?
In my case, I'm using tabs as the main navigation system, So in theory I should have something like this:
const TabLink = ({ onClick, href, isActive, label }) =>
<Tab
label={label}
onActive={onClick}
/>
export default class NavBar extends React.Component {
render () {
return (
<Tabs>
<Link to="/">{params => <TabLink label="Home" {...params}/>}</Link>
<Link to="/shop">{params => <TabLink label="shop" {...params}/>}</Link>
<Link to="/gallery">{params => <TabLink label="gallery" {...params}/>}</Link>
</Tabs>
)
}
}
But when it renders, material-ui throws an error that the child of Tabs must be a Tab component. What could be the way to proceed? How do I manage the isActive prop for the tab?
Thanks in advance
Another solution (https://codesandbox.io/s/l4yo482pll) with no handlers nor HOCs, just pure react-router and material-ui components:
import React, { Fragment } from "react";
import ReactDOM from "react-dom";
import Tabs from "#material-ui/core/Tabs";
import Tab from "#material-ui/core/Tab";
import { Switch, Route, Link, BrowserRouter, Redirect } from "react-router-dom";
function App() {
const allTabs = ['/', '/tab2', '/tab3'];
return (
<BrowserRouter>
<div className="App">
<Route
path="/"
render={({ location }) => (
<Fragment>
<Tabs value={location.pathname}>
<Tab label="Item One" value="/" component={Link} to={allTabs[0]} />
<Tab label="Item Two" value="/tab2" component={Link} to={allTabs[1]} />
<Tab
value="/tab3"
label="Item Three"
component={Link}
to={allTabs[2]}
/>
</Tabs>
<Switch>
<Route path={allTabs[1]} render={() => <div>Tab 2</div>} />
<Route path={allTabs[2]} render={() => <div>Tab 3</div>} />
<Route path={allTabs[0]} render={() => <div>Tab 1</div>} />
</Switch>
</Fragment>
)}
/>
</div>
</BrowserRouter>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
My instructor helped me with using React Router 4.0's withRouter to wrap the Tabs component to enable history methods like so:
import React, {Component} from "react";
import {Tabs, Tab} from 'material-ui';
import { withRouter } from "react-router-dom";
import Home from "./Home";
import Portfolio from "./Portfolio";
class NavTabs extends Component {
handleCallToRouter = (value) => {
this.props.history.push(value);
}
render () {
return (
<Tabs
value={this.props.history.location.pathname}
onChange={this.handleCallToRouter}
>
<Tab
label="Home"
value="/"
>
<div>
<Home />
</div>
</Tab>
<Tab
label="Portfolio"
value="/portfolio"
>
<div>
<Portfolio />
</div>
</Tab>
</Tabs>
)
}
}
export default withRouter(NavTabs)
Simply add BrowserRouter to index.js and you're good to go.
The error you are seeing from material-ui is because it expects to have a <Tab> component rendered as direct child of <Tabs> component.
Now, here is a way that I've found to integrate the link into the <Tabs> component without loosing the styles:
import React, {Component} from 'react';
import {Tabs, Tab} from 'material-ui/Tabs';
import {Link} from 'react-router-dom';
export default class MyComponent extends Component {
render() {
const {location} = this.props;
const {pathname} = location;
return (
<Tabs value={pathname}>
<Tab label="First tab" containerElement={<Link to="/my-firs-tab-view" />} value="/my-firs-tab-view">
{/* insert your component to be rendered inside the tab here */}
</Tab>
<Tab label="Second tab" containerElement={<Link to="/my-second-tab-view" />} value="/my-second-tab-view">
{/* insert your component to be rendered inside the tab here */}
</Tab>
</Tabs>
);
}
}
To manage the 'active' property for the tabs, you can use the value property in the <Tabs> component and you also need to have a value property for each tab, so when both of the properties match, it will apply the active style to that tab.
Solution with Tab highlight, Typescript based and works well with react-route v5:
Explanation: <Tab/> here work as a link to React router. Values in <Tab/> to={'/all-event'} and value={'/all-event'} should be same in order to highlgiht
import { Container, makeStyles, Tab, Tabs } from '#material-ui/core';
import React from 'react';
import {
Link,
Route,
Switch,
useLocation,
Redirect,
} from 'react-router-dom';
import AllEvents from './components/AllEvents';
import UserEventsDataTable from './components/UserEventsDataTable';
const useStyles = makeStyles(() => ({
container: {
display: 'flex',
justifyContent: 'center',
},
}));
function App() {
const classes = useStyles();
const location = useLocation();
return (
<>
<Container className={classes.container}>
<Tabs value={location.pathname}>
<Tab
label='All Event'
component={Link}
to={`/all-event`}
value={`/all-event`}
/>
<Tab
label='User Event'
component={Link}
to={`/user-event`}
value={`/user-event`}
/>
</Tabs>
</Container>
<Switch>
<Route path={`/all-event`}>
<AllEvents />
</Route>
<Route path={`/user-event`}>
<UserEventsDataTable />
</Route>
<Route path={`/`}>
<Redirect from='/' to='/all-event' />
</Route>
</Switch>
</>
);
}
export default App;
Here's another solution, using the beta of Material 1.0 and adding browser Back/Forward to the mix:
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import AppBar from 'material-ui/AppBar';
import Tabs, { Tab } from 'material-ui/Tabs';
import { withRouter } from "react-router-dom";
import Home from "./Home";
import Portfolio from "./Portfolio";
function TabContainer(props) {
return <div style={{ padding: 20 }}>{props.children}</div>;
}
const styles = theme => ({
root: {
flexGrow: 1,
width: '100%',
marginTop: theme.spacing.unit * 3,
backgroundColor: theme.palette.background.paper,
},
});
class NavTabs extends React.Component {
state = {
value: "/",
};
componentDidMount() {
window.onpopstate = ()=> {
this.setState({
value: this.props.history.location.pathname
});
}
}
handleChange = (event, value) => {
this.setState({ value });
this.props.history.push(value);
};
render() {
const { classes } = this.props;
const { value } = this.state;
return (
<div className={classes.root}>
<AppBar position="static" color="default">
<Tabs
value={value}
onChange={this.handleChange}
scrollable
scrollButtons="on"
indicatorColor="primary"
textColor="primary"
>
<Tab label="Home" value = "/" />
<Tab label="Portfolio" value = "/portfolio"/>
</Tabs>
</AppBar>
{value === "/" && <TabContainer>{<Home />}</TabContainer>}
{value === "/portfolio" && <TabContainer>{<Portfolio />}</TabContainer>}
</div>
);
}
}
NavTabs.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withRouter(withStyles(styles)(NavTabs));
You can use browserHistory instead of React-Router Link component
import { browserHistory } from 'react-router'
// Go to /some/path.
onClick(label) {
browserHistory.push('/${label}');
}
// Example for Go back
//browserHistory.goBack()
<Tabs>
<Tab
label={label}
onActive={() => onClick(label)}
/>
</Tabs>
As you see you can simply push() your target to the browserHistory
As #gkatchmar says you can use withRouter high-order component but you can also use context API. Since #gkatchmar showed withRouter already I will only show context API. Bear in mind that this is an experimental API.
https://stackoverflow.com/a/42716055/3850405
import React, {Component} from "react";
import {Tabs, Tab} from 'material-ui';
import * as PropTypes from "prop-types";
export class NavTabs extends Component {
constructor(props) {
super(props);
}
static contextTypes = {
router: PropTypes.object
}
handleChange = (event: any , value: any) => {
this.context.router.history.push(value);
};
render () {
return (
<Tabs
value={this.context.router.history.location.pathname}
onChange={this.handleChange}
>
<Tab
label="Home"
value="/"
>
<div>
<Home />
</div>
</Tab>
<Tab
label="Portfolio"
value="/portfolio"
>
<div>
<Portfolio />
</div>
</Tab>
</Tabs>
)
}
}
Here's a simple solution using the useLocation hook. No state needed. React router v5 though.
import { Tab, Tabs } from '#material-ui/core';
import { matchPath, NavLink, useLocation } from 'react-router-dom';
const navItems = [
{
id: 'one',
path: '/one',
text: 'One',
},
{
id: 'two',
path: '/two',
text: 'Two',
},
{
id: 'three',
path: '/three',
text: 'Three',
},
];
export default function Navigation() {
const { pathname } = useLocation();
const activeItem = navItems.find((item) => !!matchPath(pathname, { path: item.path }));
return (
<Tabs value={activeItem?.id}>
{navItems.map((item) => (
<Tab key={item.id} value={item.id} label={item.text} component={NavLink} to={item.path} />
))}
</Tabs>
);
}
<BrowserRouter>
<div className={classes.root}>
<AppBar position="static" color="default">
<Tabs
value={this.state.value}
onChange={this.handleChange}
indicatorColor="primary"
textColor="primary"
fullWidth
>
<Tab label="Item One" component={Link} to="/one" />
<Tab label="Item Two" component={Link} to="/two" />
</Tabs>
</AppBar>
<Switch>
<Route path="/one" component={PageShell(ItemOne)} />
<Route path="/two" component={PageShell(ItemTwo)} />
</Switch>
</div>
I've created this hook to help control the tabs and generate the default value that catches from the location URL.
const useTabValue = (array, mainPath = "/") => {
const history = useHistory();
const { pathname } = useLocation();
const [value, setValue] = useState(0);
const pathArray = pathname.split("/");
function handleChange(_, nextEvent) {
setValue(nextEvent);
history.push(`${mainPath}/${array[nextEvent]}`);
}
const findDefaultValue = useCallback(() => {
return array.forEach((el) => {
if (pathArray.indexOf(el) > 0) {
setValue(array.indexOf(el));
return;
}
});
}, [pathArray, array]);
useEffect(() => {
findDefaultValue();
}, [findDefaultValue]);
return {
handleChange,
value,
};
};
then I have used it like this :
const NavigationBar = () => {
const classes = useStyles();
const allTabs = useMemo(() => ["home", "search"]);
const { handleChange, value } = useTabValue(allTabs, "/dashboard");
return (
<div className={classes.navBarContainer}>
<Tabs
centered
value={value}
variant="fullWidth"
onChange={handleChange}
className={classes.navBar}
>
<Tab color="textPrimary" icon={<HomeIcon />} />
<Tab color="textPrimary" icon={<ExploreIcon />} />
</Tabs>
</div>
);
};
I solved this in a much easier fashion (I was surprised this worked so well - maybe there's a problem I haven't found out). I'm using Router 6 and React 17 (I know these packages are newer).
In any case, I just used the useNavigate hook in the handleChange function. Thus, now there is NO need for Switch and the code becomes much simpler. See below:
let navigate = useNavigate();
const [selection, setSelection] = useState();
const handleChange = (event, newValue) => {
setSelection(newValue);
navigate(`${newValue}`);
}
return (
<Tabs value={selection} onChange={handleChange}>
<Tab label="Products" value="products" />
<Tab label="Customers" value="customers" />
<Tab label="Invoices" value="invoices" />
</Tabs>
);
}
The handleChange function updates 'selection' which controls the display of the tabs, and also navigates to the right path.
if you set the component somewhere in your React space, and set correctly a :style route (as explained by React Router: https://reactrouter.com/docs/en/v6/getting-started/overview), you can also control in which area of the page will the content be rendered. Hope it helps somebody!
I got it working this way in my app:
import React, {useEffect, useRef} from 'react';
import PropTypes from 'prop-types';
import {makeStyles} 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';
import Container from "#material-ui/core/Container";
import {Link} from "react-router-dom";
import MenuIcon from "#material-ui/icons/Menu";
import VideoCallIcon from "#material-ui/icons/VideoCall";
const docStyles = makeStyles(theme => ({
root: {
display: 'flex',
'& > * + *': {
marginLeft: theme.spacing(2),
},
},
appBarRoot: {
flexGrow: 1,
},
headline: {
marginTop: theme.spacing(2),
},
bodyCopy: {
marginTop: theme.spacing(1),
fontSize: '1.2rem',
},
tabContents: {
margin: theme.spacing(3),
},
}));
function TabPanel(props) {
const {children, value, index, classes, ...other} = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && (
<Container>
<Box className={classes.tabContents}>
{children}
</Box>
</Container>
)}
</div>
);
}
function a11yProps(index) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
function TabOneContents(props) {
const {classes} = props;
return (
<>
<Typography variant="h4" component={'h1'} className={classes.headline}>
Headline 1
</Typography>
<Typography variant="body1" className={classes.bodyCopy}>
Body Copy 1
</Typography>
</>
)
}
function TabTwoContents(props) {
const {classes} = props;
const nurseOnboardingPath = '/navigator/onboarding/' + Meteor.userId() + '/1';
return (
<>
<Typography variant="h4" component={'h1'} className={classes.headline}>
Headline 2
</Typography>
<Typography variant="body1" className={classes.bodyCopy}>
Body Copy 2
</Typography>
</>
)
}
export default function MUITabPlusReactRouterDemo(props) {
const {history, match} = props;
const propsForDynamicClasses = {};
const classes = docStyles(propsForDynamicClasses);
const [value, setValue] = React.useState(history.location.pathname.includes('/tab_2') ? 1 : 0);
const handleChange = (event, newValue) => {
setValue(newValue);
const pathName = '/' + (value == 0 ? 'tab_1' : 'tab_2');
history.push(pathName);
};
return (
<div className={classes.appBarRoot}>
<AppBar position="static" color="transparent">
<Tabs value={value} onChange={handleChange} aria-label="How It Works" textColor="primary">
<Tab label="Tab 1" {...a11yProps(0)} />
<Tab label="Tab 2" {...a11yProps(1)} />
</Tabs>
</AppBar>
<TabPanel value={value} index={0} classes={classes}>
<TabOneContents classes={classes}/>
</TabPanel>
<TabPanel value={value} index={1} classes={classes}>
<TabTwoContents classes={classes}/>
</TabPanel>
</div>
);
}
...and in React Router:
[.....]
<Route exact path="/tab_1"
render={(routeProps) =>
<MUITabPlusReactRouterDemo history={routeProps.history}
/>
}/>
<Route exact path="/tab_2"
render={(routeProps) =>
<MUITabPlusReactRouterDemo history={routeProps.history} />
}/>
[.....]

Resources