The default behavior of the <Tab /> component seems to be to remove it's active <Tab.Pane> from the DOM every time it is made inactive, and to push it back in the DOM when it is selected and made active. This re-renders the child components of <Tab.Pane> and is a problem if you have children like Leaflet or CesiumJS which internally manage their display state, as this state is reset on every re-render.
One should expect that the default behavior would be to keep all Tab.Pane children rendered and in the DOM, and only display the one that is active. The inactive tabs should be hidden from the display while keeping them in the DOM.
Inactive Tabs get removed from the DOM and pushed in again when made active. This behavior breaks the flow of many stateful child components, by resetting their internal state.
v0.71.0
Any quick fixes would be appreciated, while this gets fixed in main.
This is expected behaviour, see this comment. I will left this open until @levithomason will response.
@layershifter It seems that either behaviour (renderActiveOnly vs renderAll) could be justified based on the use case. If the use case involves heavy context switch penalty between tabs, renderActiveOnly behaviour would make Semantic UI unusable, and require careful unmounting/remounting support for Tab children.
BlueprintJS implements a 'renderActiveTabPanelOnly' prop to support both behaviours. It would be nice to see something like this in Semantic UI's <Tab /> component, unless there is another way to use Semantic UI to mimic Tab behaviour, but keep all tabs rendered.
I managed to get my desired behavior working by using a combination of <Menu /> and <Segment />.
I set the CSS visibility property of the Segments to toggle based on the active Menu Item. Still, a little roundabout way to achieve this behavior, but it works. Looking forward to having a prop enabling this OTB.
Pasting code here for the benefit of anyone facing a similar issue with using <Tab />.
JSX
import React, { Component } from 'react';
import { Menu, Segment } from 'semantic-ui-react';
import classNames from 'classnames';
import Map from '../containers/Map/Map';
import Dashboard from '../containers/Dashboard/Dashboard';
import Team from '../containers/Team/Team';
import styles from './MainTabs.css';
class MainTabs extends Component {
constructor(props) {
super(props);
this.state = {
activePage: 'Map'
}
}
handleMenuClick = (e, { name }) => {
this.setState({ activePage: name });
}
render() {
const { activePage } = this.state;
let MapClass = classNames({
'hidden': (activePage !== 'Map')
});
let DashboardClass = classNames({
'hidden': (activePage !== 'Dashboard')
});
let TeamClass = classNames({
'hidden': (activePage !== 'Team')
});
return (
<div>
<Menu>
<Menu.Item
name="Map"
active={activePage === 'Map'}
onClick={this.handleMenuClick}
/>
<Menu.Item
name="Dashboard"
active={activePage === 'Dashboard'}
onClick={this.handleMenuClick}
/>
<Menu.Item
name="Team"
active={activePage === 'Team'}
onClick={this.handleMenuClick}
/>
</Menu>
<Segment className={MapClass}>
<Map />
</Segment>
{/* ................. Similarly for other tabs................... */}
</div>
);
}
}
export default MainTabs;
CSS
.hidden {
visibility: hidden;
}
It seems that either behaviour (
renderActiveOnlyvsrenderAll) could be justified
I would be OK supporting a single prop renderActiveOnly and default it to true so we have backward compatibility. Users can then renderActiveOnly={false} to render every tab.
Right now, we hard code the active className in TabPane.js. This will now need to become a prop. The Tab.js will then be able to choose to render all TabPanes and toggle the active prop or render a single Tab only (as it does currently).
A more adaptable quick-fix for the problem at hand starting from the implementation above. For this to work every child element put inside
import React, { Component } from "react";
import { Menu, Segment } from "semantic-ui-react";
import PropTypes from "prop-types";
class MainTabs extends Component {
state = {
activePage: ""
};
componentWillMount() {
let { children } = this.props;
this.setState({
activePage: children[0].props.name
});
}
handleMenuClick = ({ name }) => {
this.setState({ activePage: name });
};
render() {
let { activePage } = this.state;
let { children } = this.props;
return (
<div>
<Menu>
{children.map((child, index) =>
<Menu.Item
key={index}
name={child.props.name}
active={activePage === child.props.name}
onClick={() => this.handleMenuClick(child.props)}
/>
)}
</Menu>
{children.map((child, index) =>
<Segment
key={index}
style={{
display: activePage !== child.props.name ? "none" : "block"
}}
>
{child}
</Segment>
)}
</div>
);
}
}
MainTabs.propTypes = {
children: PropTypes.arrayOf(PropTypes.element).isRequired
};
export default MainTabs;
Usage example
<MainTab>
<TabOne name="Tab one label name" key={0} />
<TabTwo name="Tab two label name" key={1} />
</MainTab>
@chinoy Did #1976 resolve your issue with Leaflet, as in your example? Leaflet is why I searched for this issue as well, yet Leaflet appears to be not rendering correctly (zoomed all the way out) when rendered on an inactive tab. It works fine when the Leaflet tab is the first active tab. I.e.
Works (Leaflet is up first):
const panes = [
{menuItem: "Map", pane: {key: 'system-map', content: <MyMapPane/>}},
{menuItem: "Description", pane: {key: 'description', content: "Some content" }}
]
Doesn't work (Leaflet is initially hidden):
const panes = [
{menuItem: "Description", pane: {key: 'description', content: "Some content" }},
{menuItem: "Map", pane: {key: 'system-map', content: <MyMapPane/>}}
]
Any solution for this?. I have tried renderActiveOnly={false} , but not working for me.
Most helpful comment
I would be OK supporting a single prop
renderActiveOnlyand default it totrueso we have backward compatibility. Users can thenrenderActiveOnly={false}to render every tab.Right now, we hard code the
activeclassName inTabPane.js. This will now need to become a prop. TheTab.jswill then be able to choose to render all TabPanes and toggle theactiveprop or render a single Tab only (as it does currently).