Skip to content

Instantly share code, notes, and snippets.

@Rahul-RB
Last active March 26, 2024 15:03
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save Rahul-RB/273dbb24faf411fa6cc37488e1af2415 to your computer and use it in GitHub Desktop.
Save Rahul-RB/273dbb24faf411fa6cc37488e1af2415 to your computer and use it in GitHub Desktop.
Dynamically add and remove tabs in Material UI (Browser tabs feature)
/* No licenses, use as pleased.
* The code here uses React Class components (ES6 classes).
* Ken Nguyen has made a hooks version of this! Please find that here: https://codesandbox.io/s/addanddelete-tabs-mui-bo7tw
* Cheers!
*/
import React, { Component } from "react";
import {
withStyles,
AppBar,
Tabs,
Tab,
Grid,
Button
} from "@material-ui/core";
import Add from "@material-ui/icons/Add";
import Close from "@material-ui/icons/Close";
import cloneDeep from "lodash/cloneDeep";
const styles = theme => ({
root: {
flexGrow: 1,
marginTop:"60px",
width: "100%",
backgroundColor: theme.palette.background.paper
},
appBar:{
color:"inherit",
backgroundColor: theme.palette.background.paper
}
});
class CustomTabs extends Component {
constructor(...args){
super(...args);
this.state = {
value: 0,
tabList : [{
key:0,
id:0,
}]
};
}
addTab = () => {
this.setState((state,props)=>{
let tabList = cloneDeep(state.tabList);
let id = tabList[tabList.length-1].id+1;
tabList.push({
key:id,
id:id,
});
return {
tabList,
}
})
}
deleteTab = (e) => {
// prevent MaterialUI from switching tabs
e.stopPropagation();
// Cases:
// Case 1: Single tab.
// Case 2: Tab on which it's pressed to delete.
// Case 3: Tab on which it's pressed but it's the first tab
// Case 4: Rest all cases.
// Also cleanup data pertaining to tab.
// Case 1:
if(this.state.tabList.length === 1){
return; // If you want all tabs to be deleted, then don't check for this case.
}
// Case 2,3,4:
let tabID = parseInt(e.target.id);
let tabIDIndex = 0;
let tabList = this.state.tabList.filter((value,index)=>{
if(value.id === tabID){
tabIDIndex = index;
}
return value.id !== tabID;
});
this.setState((state,props)=>{
let curValue = parseInt(state.value);
if(curValue === tabID){
// Case 3:
if(tabIDIndex === 0){
curValue = state.tabList[tabIDIndex+1].id
}
// Case 2:
else{
curValue = state.tabList[tabIDIndex-1].id
}
}
return {
value:curValue
}
},()=>{
this.setState({
tabList:tabList
})
});
}
handleTabChange = (event, value) => {
this.setState({ value });
}
render() {
const { classes } = this.props;
const { value } = this.state;
// console.log(this.state);
return (
<AppBar position="static" className={classes.appBar}>
<Grid
container
alignItems="center"
justify="center"
>
<Grid
item
xl={11}
lg={11}
md={11}
sm={11}
xs={11}
>
<Tabs
value={value}
onChange={this.handleTabChange}
indicatorColor="primary"
textColor="primary"
variant="scrollable"
scrollButtons="auto"
>
{
this.state.tabList.map((tab)=>(
<Tab
key={tab.key.toString()}
value={tab.id}
label={"Node "+tab.id}
icon={
<Close
id={tab.id}
onClick={
this.deleteTab
}
/>
}
className="mytab"
/>
))
}
</Tabs>
</Grid>
<Grid
item
xl={1}
lg={1}
md={1}
sm={1}
xs={1}
>
<Button
variant="outlined"
onClick={this.addTab}
>
<Add/>
</Button>
</Grid>
</Grid>
</AppBar>
);
}
}
export default withStyles(styles)(CustomTabs);
@Rahul-RB
Copy link
Author

Hi @Rahul-RB, thank you for the work. I was struggling finding a way to delete tabs with MUI and I found yours. It worked perfectly. Since your code is working with React Class Component, I have re-written it with React Functional Component and Hooks.
Here is a reproduction. I also mentioned your work there :D

https://codesandbox.io/s/addanddelete-tabs-mui-bo7tw

Hi,
No issues and thanks for the mention!
I think the Hooks version would server better for future proofing (given its usefulness in unit testing and ease of reading).
I'll link your codesandbox.io link in my gist.

@atomash13
Copy link

atomash13 commented Aug 3, 2021

Hi! Your code helped me out for real! Thanks a lot for sharing.

I have a suggestion. When you click on an 'x' button, sometimes it doesn't work because of the svg children <path/> tag. So I added this in the styles so that the onClick event doesn't trigger it.

path{
    pointer-events: none;
}

Took the hint from this article.

@Rahul-RB
Copy link
Author

Rahul-RB commented Aug 3, 2021

@atomash13
Ah yes, I did observe that the X button didn't work exactly, thank you for pointing out a fix!

Happy to help out! :)

@yadavendra15
Copy link

Hey @Rahul-RB ,

First of all thanks you for posting such amazing code.

In the lines 87 - 107, you've used a second callback..
but in my code I'm not using class component.

So, I'm getting error "Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect()."

What will be the possible way to do so without callback. Can you please help me out of this.

Thanks in advance.

@Rahul-RB
Copy link
Author

@yadavendra15 thank you, could you please "star" this gist.

I remember @KenNguyen-0107 had created a hooks version of this here: https://codesandbox.io/s/addanddelete-tabs-mui-bo7tw

Could you try that and let me know if that works?

@yadavendra15
Copy link

Sure Rahul,
Thanks for it

@yadavendra15
Copy link

Hey @Rahul-RB ,

Can you Please help me in https://codesandbox.io/s/thirsty-joliot-pjhkd?file=/src/AddAndDeleteTab.js

actually, I want to add a Whiteboard in those tabs..
Now, I'm stuck at one point..

When I draw something in the whiteboard and switch the tabs, then my tabpanel re-renders, but I don't want to do so. I want to avoid the re-render of tabpanel.

And my second issue is, Suppose I draw something on WhiteBoard1 and I close that tab 1.. then, that content will be shifted to the next tab, and shows as the content of tab 2..

Please help me in this..

@smarajitWOW
Copy link

Can the tabs be renamed?

@pereiraryan
Copy link

hey @Rahul-RB do we have an example where there are contents to these tabs?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment