Chakra UI, Tabs onChange method throws Maximum update depth exceeded on state change - javascript

I have the following code,
React throws Maximum update depth exceeded error, whenever I change the change the
index, It is somehow forming an infinite loop,
and the onChange method is being called on render, how can I fix it?
const ChecklistNavbarTabs = () => {
const [tabIndex, setTabIndex] = useState(0);
return (
<Tabs
variant='unstyled'
alignItems='center'
width='full'
bgColor='header.100'
height='full'
onChange={(index:number)=>{setTabIndex(index)}}
>
<TabList color='blackAlpha.600' justifyContent='space-between' m='3'>
<Flex
alignContent='space-between'
borderRadius='4'
justifyContent='space-between'
border='1px solid'
borderColor='blackAlpha.500'
>
<Flex justifyContent='space-between'>
<Flex>
<Tab
bgColor='gray.100'
borderColor='blackAlpha.500'
border='1px solid'
>
<Text>Header Fields</Text>
</Tab>
</Flex>
<Flex>
<Tab
bgColor='gray.100'
borderColor='blackAlpha.500'
border='1px solid'
>
<Text>Checkpoint</Text>
</Tab>
</Flex>
<Flex>
<Tab
bgColor='gray.100'
borderColor='blackAlpha.500'
border='1px solid'
>
<Text>Checklist Item</Text>
</Tab>
</Flex>
<Flex>
<Tab
bgColor='gray.100'
borderColor='blackAlpha.500'
border='1px solid'
>
<Text>Footer Field</Text>
</Tab>
</Flex>
</Flex>
</Flex>
<Flex justifyContent='flex-end' flex='1'>
<AddHeaderFieldButton />
</Flex>
</TabList>
<Flex>
<TabPanels>
<TabPanel>
<ChecklistHeaderFieldTable table={navbarData} />
</TabPanel>
<TabPanel>
<CreateCheckpointTable table={CreateCheckPointData} />
</TabPanel>
<TabPanel>
{/* <CreateChecklistItemTable table={AddChecklistItemData} /> */}
</TabPanel>
<TabPanel>
{/* <ChecklistFooterTable table={AddFooterTableData} /> */}
</TabPanel>
</TabPanels>
</Flex>
</Tabs>
);
};
export default ChecklistNavbarTabs;
I have tried, useMemo and useCallback as well, to memoize the results and stop re-render though I don't know if that the right way.
Please help me solve this error.

The documentation says the following:
You can render any element within Tabs, but TabList should only have Tab as children, and TabPanels should have TabPanel as children.
Tabs expects TabList and TabPanels as children. The order doesn't matter, you can have TabList at the top, at the bottom, or both.
I believe nesting Tab inside of multiple Flex components is the cause of the onChange infinite loop issue. Tab should be a direct child of TabList. You'd likely also need to remove <AddHeaderFieldButton /> or place it inside of a Tab component. Also TabPanels should not be wrapped in a Flex component.

The cause of the problem was that the tab panels had react-table tables as children, and there was an issue that I did not use useMemo while declaring table headers.
simply then fixed it via useMemo

Related

Material-UI : tab not responsive with a datatable inside

I use the Simple Tabs from Material-UI and I have one Tab how contain a Datatable (React-Data_Table) and this tab is not responsive like other when the table is full
Empty
Full
The code
<Grid item xs={12}>
<Paper className={classes.paper}>
<AppBar position="static" className={classes.appBar}>
<Tabs value={value} onChange={handleChange} classes={{ indicator: classes.indicator }} variant="fullWidth">
<Tab label="Tableau" {...a11yProps(0)} />
<Tab label="Graphique" {...a11yProps(1)} />
<Tab label="Carte" {...a11yProps(2)} />
</Tabs>
</AppBar>
<TabPanel value={value} index={0}>
<Table sections={props.sections}/>
</TabPanel>
<TabPanel value={value} index={1}>
<Graph />
</TabPanel>
<TabPanel value={value} index={2}>
<AppMap />
</TabPanel>
</Paper>
</Grid>
I don't know how to fix this except maybe make a CSS to change width with size screen
Thanks for the help
If the child element is exceeding it's parent's size(container element) I would recommend to try using max-width to ensure the width doesn't pass a certain limit.

How to place a tooltip for the react-bootstrap tab component

I have three tabs , I need to place a tool tip on hover on those tabs,I am using bootstrap tabs.(Its a react application.)
import { Tabs,Tab} from "react-bootstrap";
// inside return
<Tabs
variant="pills"
className="mb-3"
activeKey={key}
>
<Tab eventKey="managed" title="Managed" Tooltip="hello"/>
<Tab eventKey="unmanaged" title="Unmanaged"/>
<Tab eventKey="source" title="Source"/>
</Tabs>
I need a tool tip appear whenever the Mouse pointer is placed on the Tab Header, for example I need a tooltip when the mouse pointer is on Managed tab .
Here are the tabs
you can use TabContainer along with OverlayTrigger to show a tooltip on top,
const TooltipTopNavItem = ({title, tooltipMessage, eventKey}) => {
return (
<OverlayTrigger
key={`${eventKey}-top`}
placement={'top'}
overlay={
<Tooltip id={`tooltip-top`}>
{tooltipMessage}
</Tooltip>
}
>
<Nav.Item>
<Nav.Link eventKey={eventKey}>{title}</Nav.Link>
</Nav.Item>
</OverlayTrigger>
)
}
and use the above custom component in the TabContainer,
<Tab.Container id="tabs-example" activeKey={key}>
<Row>
<Col sm={3}>
<Nav variant="pills" className="flex-column">
<TooltipTopNavItem title={'Managed'} tooltipMessage={'Managed tooltip'} eventKey={"managed"} />
<TooltipTopNavItem title={'Unmanaged'} tooltipMessage={'Unmanaged tooltip'} eventKey={"unmanaged"} />
<TooltipTopNavItem title={'Source'} tooltipMessage={'Source tooltip'} eventKey={"source"} />
</Nav>
</Col>
<Col sm={9}>
<Tab.Content>
<Tab.Pane eventKey="managed">
In managed tab
</Tab.Pane>
<Tab.Pane eventKey="unmanaged">
In Unmanaged tab
</Tab.Pane>
<Tab.Pane eventKey="source">
In source tab
</Tab.Pane>
</Tab.Content>
</Col>
</Row>
</Tab.Container>
this is just an example, you can modify the TabContainer and TooltipTopNavItem based on your use-case.

Avoid re-rendering in React when switching between tabs

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>
);
}

Array map function renders correct but calls function with last element rendered

I am using the map function to iterate through a JSON Array which looks similar to this
table=[
{"name":"jon snow",
"house":"stark"
},
{
"name":"arya",
"house":"stark"
},
{"name":"cersei",
"house":"lannister"
}
]
Map function renders the items perfectly however, the onClick handler seems to pass only the last item of the array.
For example, the text items are rendered to display the appropriate name, however, on clicking their individual action button seems to pass only the last element.
Here, after triggering the event handler "handleClose", I logged the "item" element printed to the console but it displays element with name "cersei" even on clicking the element with name "jon snow".
The syntax seems to be fine for the event handler for React afaik. And, since the variable "item" is also local variable within the function. It shouldn't display the last item in the array. Is there anything I am missing over here?
{/*Event Handler*/}
function handleClose(item) {
console.log(item)
setAnchorEl(null);
}
{/*Few lines of code after in the return function of React*/}
var table2=[
{"name":"jon snow",
"house":"stark"
},
{"name":"arya",
"house":"stark"
},
{"name":"cersei",
"house":"lannister"
}
]
console.log(table);
return(
<div>
<div>
</div>
{/*Actual table data*/}
<div>
<Card style={{width:'100%'}}>
<Card.Body>
<div >
{table2.map((item,index)=>{
let tempElement =item;
return(
<div >
<Grid container spacing={0}>
<Grid item xs={2} >
<div style={{backgroundColor:'#fff'}}>
<ListItem alignItems="flex-start">
<ListItemAvatar>
<Avatar src={item.picture} />
</ListItemAvatar>
<ListItemText style={{wordBreak:'normal'}}
primary={item.name}
secondary={
<React.Fragment>
<Typography
component="span"
variant="body2"
className={classes.inline}
color="textPrimary"
>
{getRoleFromData(item.roles)}
</Typography>
</React.Fragment>
}
/>
</ListItem>
</div>
</Grid>
{/*View/Edit buttons */}
<Grid item xs style={{backgroundColor:'#fff'}} >
<div>
<Button aria-controls="simple-menu" aria-haspopup="true" onClick={handleClick}>Action</Button>
<ThemeProvider>
<Menu
id="simple-menu"
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem><Button onClick={()=>handleClose(item)}>View</Button></MenuItem>
<MenuItem><Button onClick={()=>handleClose(item)}>Edit</Button></MenuItem>
</Menu>
</ThemeProvider>
</div>
</Grid>
</Grid>
<div>
<Divider variant="inset"/>
</div>
</div>
);
})
}
</div>
</Card.Body>
</Card>
</div>
</div>

There is Proxy-object instead just index when change tab in materia-ui/Tabs?

I try to use material-ui tabs => Fixed Tabs => Full width tabs panel
I have function component OvertiemsReport with Tabs:
const TabContainer = ({children, dir}) => (
<Typography component="div" dir={dir} style={{padding: 8 * 3}}>
{children}
</Typography>
);
const OvertimesReport = ({preview = false, tabValue, handleChange, classes}) => (
<div className={classes.workarea}>
<Card>
<CardContent className="overtimes">
<Typography variant="display2">Отчет о переработках</Typography>
<Grid container spacing={24} className={classes.gridContainer}>
<Grid item>
{preview}
<div className={classes.root}>
<AppBar position="static" color="default">
<Tabs
value={tabValue}
onChange={handleChange}
indicatorColor="primary"
textColor="secondary"
fullWidth
>
<Tab label="Item One"/>
<Tab label="Item Two"/>
</Tabs>
</AppBar>
</div>
</Grid>
</Grid>
</CardContent>
</Card>
</div>
);
and I have external handeChange function
handleChange: value => dispatch(actions.handleChangeTab(value)),
and value stored in redux.
So, after I click to change tab, in input of handeChange I recieve Proxy-object instead just a number of chose tab
Received object in handleChange method like this:
The function given to the onChange prop of the Tabs component is called with the event and value as arguments.
It will work if you change your event handler to this:
handleChange: (event, value) => dispatch(actions.handleChangeTab(value)),

Categories