1

I built a simplistic tab container in ReactJS using the idea that the container component keeps in its state an integer index denoting the tab pane to display and then renders only the child (from the this.props.children array) that is found at that index position.

The gist of this approach was:

const TabContainer = React.createClass({
    props: {
        tabNames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired
    },
    getInitialState: function() {
        return {
            activeIndex: 0
        };
    },
    setTab: function(n) {
        this.setState({activeIndex: n});
    },
    render: function render() {
        const childToRender = this.props.children[this.state.activeIndex];
        return (
                <div className='tab-container'>
                <Tabs
                    tabNames= {this.props.tabNames}
                    active  = {this.state.active}
                    setTab  = {this.setTab}
                />
                <div className='tab-pane'>
                {childToRender}
                </div>
                </div>
        );
    }
});

I.e. only the indexed child is selected and rendered (I've omitted for the sake of simplicity the code handling the edge case where this.props.children is not an Array).

I found out that this approach was unsatisfactory as when the user selected different tabs, the component corresponding to the pane to render was mounted and unmounted repeatedly and any state that the panes had could not be preserved.

Ultimately, I used an approach in which all children are rendered and all panes, except the selected one, are assigned a class hidden that is then used to style those panes as: display:none. When this later solution was used my panes remained mounted even after the user clicked through the various tabs and any state they had wasn't lost as the user cycled through the tabs.

My questions are:

  1. was the initial approach (where only a specific child is rendered) an anti-pattern or was there some other mechanism I could have used to preserve the state of the individual panes or prevent them from being unmounted?
  2. if the initial approach was indeed an anti-pattern how can that anti-pattern be expressed more generally?
m0meni
  • 16,006
  • 16
  • 82
  • 141
Marcus Junius Brutus
  • 26,087
  • 41
  • 189
  • 331
  • It may be worth noting in your question whether you'd be interested in utilizing additional libraries intended to manage application state (e.g. redux) or if you want to remain a purely reactjs application. – Conspicuous Compiler Jun 01 '16 at 19:52

1 Answers1

1

I don't think the initial approach was an antipattern at all. Choosing whether or not to mount/unmount in your logic is just dependent on your circumstances. If you want state preserved, then don't unmount. If you want a fresh element, complete with a call to getInitialState, then unmounting is the right way to go.

As an easy counterexample, consider React-Router. The Router completely unmounts/remounts components on route change. And route changing is effectively just a higher order of tabbing.

But given the situation where you want state preserved, I think your solution is the proper one. You might want to take a look at material-ui, which does something very similar in their tabbing: https://github.com/callemall/material-ui/blob/master/src/Tabs/TabTemplate.js

Jake Haller-Roby
  • 6,335
  • 1
  • 18
  • 31
  • Nice. I am wondering whether `height=0; overflow='hidden';` (used in the link you provided) is any different than `display:none` in terms of triggering (or not) specific ReactJS life-cycle events or is purely stylistic. – Marcus Junius Brutus Jun 02 '16 at 08:20
  • @MarcusJuniusBrutus In some browsers, there are use cases where display none has some unexpected side effects, where for example an img won't load until that container is displayed. or maybe just this problem here https://stackoverflow.com/a/13038091/614277 – Abderrahmane TAHRI JOUTI Nov 05 '17 at 16:30