I have a react application using material-ui to create tabs.
<div className={classes.root}>
<AppBar position="static">
<Tabs value={value} onChange={handleChange}>
<Tab label="Item One" />
<Tab label="Item Two" />
<Tab label="Item Three" />
</Tabs>
</AppBar>
{value === 0 && <TabContainer id={1}>Item One</TabContainer>}
{value === 1 && <TabContainer id={2}>Item Two</TabContainer>}
{value === 2 && <TabContainer id={3}>Item Three</TabContainer>}
</div>
The TabContainer is a functional component and does some heavy computation.
Is it possible to prevent TabContainer from re-rendering when switching between tabs?
Update:
Check my answer for a solution with React functional components and css classes.
In order to prevent TabContainer from re-rendering. You have to
Render all TabContainer data at once instead of rendering based on value.
You have to play with CSS and have to display only that tab which is currently active.
Also you can make your component as PureComponent or you can override shouldComponentUpdate() lifecycle method to stop extra re-rendering of your react component.
Update/Partial Solution:
With the below code (based on Rahul Jain's answer) using css classes to display the active TabContainer, the memoized functions seems to be really memoized.
const useTabContainerStyles = makeStyles((theme: Theme) => createStyles({
root: {
padding: 8 * 3
},
tabcontainerInActive: {
display: "none"
}
})
);
function TabContainer(props: TabContainerProps) {
const styles = useTabContainerStyles({});
console.log("In TabContainer");
const doubleValue = useMemo(() => double(props.id), [props.id]);
return (
<Typography
id={props.id.toString()}
component="div"
className={classnames(styles.root, {
[styles.tabcontainerInActive]: !props.active
})}
>
{props.children + " " + doubleValue}
</Typography>
);
}
export default function SimpleTabs() {
const classes = useStyles({});
const [selectedTab, setSelectedTab] = React.useState(0);
function handleChange(event: React.ChangeEvent<{}>, newValue: number) {
setSelectedTab(newValue);
}
return (
<div className={classes.root}>
<AppBar position="static">
<Tabs value={selectedTab} onChange={handleChange}>
<Tab label="Item One" />
<Tab label="Item Two" />
<Tab label="Item Three" />
</Tabs>
</AppBar>
{/* */}
<TabContainer id={0} active={selectedTab === 0}>
Item One
</TabContainer>
<TabContainer id={1} active={selectedTab === 1}>
Item Two
</TabContainer>
<TabContainer id={2} active={selectedTab === 2}>
Item Three
</TabContainer>
</div>
);
}
Related
I am creating custom hook of accordion component . I am getting below error
export default const Tabs: OverridableComponent<TabsTypeMap<{}, ExtendButtonBase<ButtonBaseTypeMap<{}, "button">>>>
here is my code
https://codesandbox.io/s/epic-easley-ek3ev5?file=/src/App.tsx
export default function BasicTabs() {
const { register, value } = useTabs();
return (
<Box sx={{ width: "100%" }}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs {...register()} aria-label="basic tabs example">
<Tab label="Item One" {...a11yProps(0)} />
<Tab label="Item Two" {...a11yProps(1)} />
<Tab label="Item Three" {...a11yProps(2)} />
</Tabs>
</Box>
<TabPanel value={value} index={0}>
Item One
</TabPanel>
<TabPanel value={value} index={1}>
Item Two
</TabPanel>
<TabPanel value={value} index={2}>
Item Three
</TabPanel>
</Box>
);
any suggestion
In your useTabs you should change change React.ChangeEvent to React.SyntheticEvent as the signature of onChange is function(event: React.SyntheticEvent, value: any) => void in <Tabs /> : https://mui.com/material-ui/api/tabs/
I want to display selected tab item into url path. I found this Material UI example:
import * as React from 'react';
import Tabs from '#mui/material/Tabs';
import Tab from '#mui/material/Tab';
import Typography from '#mui/material/Typography';
import Box from '#mui/material/Box';
interface TabPanelProps {
children?: React.ReactNode;
index: number;
value: number;
}
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`vertical-tabpanel-${index}`}
aria-labelledby={`vertical-tab-${index}`}
{...other}
>
{value === index && (
<Box sx={{ p: 3 }}>
<Typography>{children}</Typography>
</Box>
)}
</div>
);
}
function a11yProps(index: number) {
return {
id: `vertical-tab-${index}`,
'aria-controls': `vertical-tabpanel-${index}`,
};
}
export default function VerticalTabs() {
const [value, setValue] = React.useState(0);
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
return (
<Box
sx={{ flexGrow: 1, bgcolor: 'background.paper', display: 'flex', height: 224 }}
>
<Tabs
orientation="vertical"
variant="scrollable"
value={value}
onChange={handleChange}
aria-label="Vertical tabs example"
sx={{ borderRight: 1, borderColor: 'divider' }}
>
<Tab label="Item One" {...a11yProps(0)} />
<Tab label="Item Two" {...a11yProps(1)} />
<Tab label="Item Three" {...a11yProps(2)} />
<Tab label="Item Four" {...a11yProps(3)} />
<Tab label="Item Five" {...a11yProps(4)} />
<Tab label="Item Six" {...a11yProps(5)} />
<Tab label="Item Seven" {...a11yProps(6)} />
</Tabs>
<TabPanel value={value} index={0}>
Item One
</TabPanel>
<TabPanel value={value} index={1}>
Item Two
</TabPanel>
<TabPanel value={value} index={2}>
Item Three
</TabPanel>
<TabPanel value={value} index={3}>
Item Four
</TabPanel>
<TabPanel value={value} index={4}>
Item Five
</TabPanel>
<TabPanel value={value} index={5}>
Item Six
</TabPanel>
<TabPanel value={value} index={6}>
Item Seven
</TabPanel>
</Box>
);
}
https://mui.com/components/tabs/#VerticalTabs.tsx
Sandbox: https://codesandbox.io/s/verticaltabs-material-demo-forked-yyqhm
I found this code example how to add url param:
https://v5.reactrouter.com/web/example/nesting
Do know how I can implement the code to display selected tab into url link. Example: https://yyqhm.csb.app/<selected_tab>
Main Solution
Check this solution out.
It utilizes react-router-dom, so wrap your root component with BrowserRouter
ReactDOM.render(
<StyledEngineProvider injectFirst>
<BrowserRouter>
<Demo />
</BrowserRouter>
</StyledEngineProvider>,
document.querySelector("#root")
);
This is the updated code - the basic idea is to use pathname as the value to check for, and update it when a tab gets picked:
import { Routes, Route, useNavigate } from "react-router-dom";
export default function VerticalTabs() {
const [value, setValue] = React.useState(0);
const navigate = useNavigate();
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
navigate(newValue);
setValue(newValue);
};
return (
<Box
sx={{
flexGrow: 1,
bgcolor: "background.paper",
display: "flex",
height: 224
}}
>
<Tabs
orientation="vertical"
variant="scrollable"
onChange={handleChange}
value={
window.location.pathname == "/"
? "/item_one"
: window.location.pathname
}
aria-label="Vertical tabs example"
sx={{ borderRight: 1, borderColor: "divider" }}
>
<Tab value="/item_one" label="Item One" {...a11yProps(0)} />
<Tab value="/item_two" label="Item Two" {...a11yProps(1)} />
<Tab value="/item_three" label="Item Three" {...a11yProps(2)} />
</Tabs>
<Routes>
<Route
path="/"
element={
<TabPanel value={value} index={0}>
Item One
</TabPanel>
}
/>
<Route
path="/item_one"
element={
<TabPanel value={value} index={0}>
Item One
</TabPanel>
}
/>
<Route
path="/item_two"
element={
<TabPanel value={value} index={1}>
Item Two
</TabPanel>
}
/>
<Route
path="/item_three"
element={
<TabPanel value={value} index={2}>
Item Three
</TabPanel>
}
/>
</Routes>
</Box>
);
}
Alternative
Here is a link to the working version of the code.
The code below reads the pathname of the current page, then sets it to state.
...
React.useEffect(() => {
setValue( +window.location.pathname.substring(1) );
}, []);
You could also define a dictionary that functions as a sort of inverted index to match custom pathnames to their related index value, or maybe a typescript enum.
{
'item-one': 1,
'item-two': 2,
'item-three': 3,
1: 'item-one',
2: 'item-two',
3: 'item-three'
}
Whenever a tab is selected, you can set the index to the path by manipulating history.
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
window.history.replaceState({}, "", "/" + newValue);
setValue(newValue);
};
...
I have an appBar and the homepage would appear behind the appbar. I wanted it to appear below it. This is what it looks like:
The AppBar codes:
const Header = () => {
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
//Breakpoints
const theme = useTheme();
const isMatch = useMediaQuery(theme.breakpoints.down("md"));
return (
<div>
<AppBar>
<Toolbar>
{/* //or just change this typography to an icon or picture */}
<Typography>Website</Typography>
{isMatch ? (
<h1>
<DrawerComponent />
</h1>
) : (
<Tabs
value={value}
indicatorColor="secondary"
onChange={handleChange}
aria-label="simple tabs example"
>
<Tab disableRipple label="Homepage" to="/" component={Link} />
<Tab disableRipple label="Login" to="/login" component={Link} />
<Tab disableRipple label="Settings" />
<Tab disableRipple label="Sample1" />
<Tab disableRipple label="Sample2" />
<Tab disableRipple label="Sample3" />
</Tabs>
)}
</Toolbar>
</AppBar>
</div>
);
};
export default Header;
I need to put a <br/> just to see the homepage:
const Homepage = (props) => {
return (
<section>
<br />
<h1>Homepage</h1>
</section>
);
};
export default Homepage;
And I have this drawerComponent for small screen sizes, it even got worse, you won't be able to see any message anymore not unless there will be a lot of <br/> before the message.
const DrawerComponent = () => {
const useStyles = makeStyles((theme) => ({
drawerContainer: {},
iconButtonContainer: {
marginLeft: "auto",
color: "white",
},
menuIconToggle: {
fontSize: "3rem",
},
link: {
textDecoration: "none",
},
}));
const [openDrawer, setOpenDrawer] = useState(false);
//Css
const classes = useStyles();
return (
<div>
<Drawer
anchor="left"
classes={{ paper: classes.drawerContainer }}
onClose={() => setOpenDrawer(false)}
open={openDrawer}
onOpen={() => setOpenDrawer(true)}
>
<List className={classes.link}>
<Link to="/">
<ListItem divider button onClick={() => setOpenDrawer(false)}>
<ListItemIcon>
<ListItemText> Homepage</ListItemText>
</ListItemIcon>
</ListItem>
</Link>
<Link to="/login">
<ListItem divider button onClick={() => setOpenDrawer(false)}>
<ListItemIcon>
<ListItemText> Login</ListItemText>
</ListItemIcon>
</ListItem>
</Link>
<ListItem divider button onClick={() => setOpenDrawer(false)}>
<ListItemIcon>
<ListItemText>Sample</ListItemText>
</ListItemIcon>
</ListItem>
<ListItem divider button onClick={() => setOpenDrawer(false)}>
<ListItemIcon>
<ListItemText> Sample</ListItemText>
</ListItemIcon>
</ListItem>
</List>
</Drawer>
<IconButton
edge="end"
className={classes.iconButtonContainer}
onClick={() => setOpenDrawer(!openDrawer)}
disableRipple
>
<MenuIcon className={classes.menuIconToggle} />
</IconButton>
</div>
);
};
export default DrawerComponent;
A way around this would be to add a margin-top or a padding-top to your homepage component equal to the height of the appbar.
Yet, a better approach would be ro use the following CSS properties on your appBar.
.app-bar {
position: sticky;
top: 0;
}
This will make your appbar stick to the top and will automatically adjust the height of its following DOM elements.
This post may answer your question: Creating a navbar with material-ui
You can either try:
Using CSS to implement padding-top (use "em" instead of "px" for a responsive padding height)
Reorganising your React components, making sure that the header (appbar) is not in the page, but rather a component at the same level (refer to the post linked above)
How do we call this tab menu with the next and preview option and also is there any material-ui component of it
As per the comment, I have prepared an example for scrollable tab using material ui. Please have a look.
import React 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';
function TabPanel(props) {
const {children, value, index, ...other} = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`scrollable-auto-tabpanel-${index}`}
aria-labelledby={`scrollable-auto-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: `scrollable-auto-tab-${index}`,
'aria-controls': `scrollable-auto-tabpanel-${index}`,
};
}
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
width: '100%',
backgroundColor: theme.palette.background.paper,
},
}));
export default function ScrollableTabsButtonAuto() {
const classes = useStyles();
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<div className={classes.root}>
<AppBar position="static" color="default">
<Tabs
value={value}
onChange={handleChange}
indicatorColor="primary"
textColor="primary"
variant="scrollable"
scrollButtons="auto"
aria-label="scrollable auto tabs example"
>
<Tab label="Item One" {...a11yProps(0)} />
<Tab label="Item Two" {...a11yProps(1)} />
<Tab label="Item Three" {...a11yProps(2)} />
<Tab label="Item Four" {...a11yProps(3)} />
<Tab label="Item Five" {...a11yProps(4)} />
<Tab label="Item Six" {...a11yProps(5)} />
<Tab label="Item Seven" {...a11yProps(6)} />
<Tab label="Item Eight" {...a11yProps(7)} />
<Tab label="Item Nine" {...a11yProps(8)} />
<Tab label="Item Ten" {...a11yProps(9)} />
</Tabs>
</AppBar>
<TabPanel value={value} index={0}>
Item One
</TabPanel>
<TabPanel value={value} index={1}>
Item Two
</TabPanel>
<TabPanel value={value} index={2}>
Item Three
</TabPanel>
<TabPanel value={value} index={3}>
Item Four
</TabPanel>
<TabPanel value={value} index={4}>
Item Five
</TabPanel>
<TabPanel value={value} index={5}>
Item Six
</TabPanel>
<TabPanel value={value} index={6}>
Item Seven
</TabPanel>
<TabPanel value={value} index={7}>
Item Eight
</TabPanel>
<TabPanel value={value} index={8}>
Item Nine
</TabPanel>
<TabPanel value={value} index={9}>
Item Ten
</TabPanel>
</div>
);
}
This type of UI elements is called "chips".
https://material-ui.com/components/chips/
Note, that usually it is not used for navigation, but rather for displaying tags, categories, etc.
I am creating tab and adding routing inside the first tab. But the desired page is not shown after a click on the link. Although after refreshing the page the corresponding page will display on screen. Anyone please help me to solve this where I'm doing wrong im using material UI for this
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<Typography
component="div"
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
<Box p={3}>{children}</Box>
</Typography>
);
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.any.isRequired,
value: PropTypes.any.isRequired,
};
function a11yProps(index) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
backgroundColor: theme.palette.background.paper,
},
}));
export default function SimpleTabs() {
const classes = useStyles();
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<div className={classes.root}>
<AppBar position="static">
<Tabs value={value} onChange={handleChange} aria-label="simple tabs example">
<Tab label="Item One" {...a11yProps(0)} />
<Tab label="Item Two" {...a11yProps(1)} />
<Tab label="Item Three" {...a11yProps(2)} />
</Tabs>
</AppBar>
<TabPanel value={value} index={0}>
<Router>
<ul className="navbar-nav mr-auto" >
<li><Link to={'/Text'} className="nav-link"><i class="fas fa-text-height"></i> Text </Link></li>
</ul>
<div>
</div>
</Router>
</TabPanel>
<TabPanel value={value} index={1}>
Item Two
</TabPanel>
<TabPanel value={value} index={2}>
Item Three
</TabPanel>
<Router>
<Switch>
<Route exact path='/Text' component={Text} />
</Switch>
</Router>
</div>
);
}
so please tell me where should i change or what is wrong with the code.
Why are you declaring your Router component twice? There should be only one Router component wrapping whole application (not talking about special cases when 2+ are needed) which is the reason your Link and your Route are not in sync. Try something like this:
return (
<Router>
<div className={classes.root}>
<AppBar position="static">
<Tabs value={value} onChange={handleChange} aria-label="simple tabs example">
<Tab label="Item One" {...a11yProps(0)} />
<Tab label="Item Two" {...a11yProps(1)} />
<Tab label="Item Three" {...a11yProps(2)} />
</Tabs>
</AppBar>
<TabPanel value={value} index={0}>
<ul className="navbar-nav mr-auto" >
<li><Link to={'/Text'} className="nav-link"><i class="fas fa-text-height"></i> Text </Link></li>
</ul>
<div>
</div>
</TabPanel>
<TabPanel value={value} index={1}>
Item Two
</TabPanel>
<TabPanel value={value} index={2}>
Item Three
</TabPanel>
<Switch>
<Route exact path='/Text' component={Text} />
</Switch>
</div>
</Router>
);