Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<Tab /> re-renders child pane on switching. #1877

Closed
chinoy opened this issue Jul 19, 2017 · 7 comments
Closed

<Tab /> re-renders child pane on switching. #1877

chinoy opened this issue Jul 19, 2017 · 7 comments
Assignees

Comments

@chinoy
Copy link

chinoy commented Jul 19, 2017

Steps

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.

Expected Result

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.

Actual Result

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.

Version

v0.71.0

Any quick fixes would be appreciated, while this gets fixed in main.

@layershifter
Copy link
Member

layershifter commented Jul 19, 2017

This is expected behaviour, see this comment. I will left this open until @levithomason will response.

@chinoy
Copy link
Author

chinoy commented Jul 19, 2017

@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.

@chinoy
Copy link
Author

chinoy commented Jul 19, 2017

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

@levithomason
Copy link
Member

It seems that either behaviour (renderActiveOnly vs renderAll) 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).

@AndreiEnache
Copy link
Contributor

AndreiEnache commented Jul 21, 2017

A more adaptable quick-fix for the problem at hand starting from the implementation above. For this to work every child element put inside should have a name prop (which is used for the menu label).

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>

@drheinheimer
Copy link

drheinheimer commented Aug 30, 2017

@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/>}}
        ]

@Pa1sathvik
Copy link

Any solution for this?. I have tried renderActiveOnly={false} , but not working for me.

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

No branches or pull requests

6 participants