Exercise 4: Solution

In notebook:
FrontEndMasters React
Created at:
2016-06-17
Updated:
2016-06-17
Tags:
libraries React JavaScript
Starting file:
  ////////////////////////////////////////////////////////////////////////////////
// Excercise:
//
// make tabs a "pure component" by not managing any of its own state, instead
// add a property to tell it which tab to show, and then have it communicate
// with its owner to get rerendered with a new active tab.
//
// Why would you move that state up? you might have a workflow where they can't
// progress from one step to the next until they've completed some sort of task
// but they can go back if they'd like. If the tabs keep their own state you
// can't control them with your application logic.
////////////////////////////////////////////////////////////////////////////////
var React = require('react');
var styles = require('./styles');
var data = require('./data');

var Tabs = React.createClass({

  propTypes: {
    data: React.PropTypes.array.isRequired,
    // 9. ++++ add proptype (so other developers immediatly know what to specify)
    onActivate: React.PropTypes.func.isRequired
  },

    // 1. ---- we don't need the State
// ----  getInitialState () {
// ----    return {
// ----      activeTabIndex: 0
// ----    };
// ----  },

  handleTabClick (activeTabIndex) {
    //   2. ++++ use props
    this.props.onActivate(activeTabIndex);
    // ---- this.setState({ activeTabIndex });
  },

  renderTabs () {
    return this.props.data.map((tab, index) => {
        // 3. ++-- change from using state (this.state.activeTabIndex)
      var style = this.props.activeTabIndex === index ? styles.activeTab : styles.tab;
      var clickHandler = this.handleTabClick.bind(this, index);
      return (
        <div key={tab.name} style={style} onClick={clickHandler}>
          {tab.name}
        </div>
      );
    });
  },

  renderPanel () {
    //   4. ++-- change this.state.activeTabIndex
    var tab = this.props.data[this.props.activeTabIndex];
    return (
      <div>
        <p>{tab.description}</p>
      </div>
    );
  },

  render () {
    return (
      <div style={styles.app}>
        <div style={styles.tabs}>
          {this.renderTabs()}
        </div>
        <div style={styles.tabPanels}>
          {this.renderPanel()}
        </div>
      </div>
    );
  }
});

var App = React.createClass({
    // 8. add getInitialState
    getInitialState () {
        return {
            activeTabIndex: 0
        }
    },
    
    // 7. add handleTab
    handleTab (activeTabIndex) {
        this.setState({activeTabIndex});
    },
    
  render () {
    return (
      <div>
        <h1>Props v. State</h1>
        <Tabs 
        // 5. ++-- add activeTabIndex attribute
            activeTabIndex={this.state.activeTabIndex} 
            data={this.props.tabs}
            // 6. ++++ add onActivateTab
            onActivateTab = {this.handleTab}
        />
      </div>
    );
  }
});

React.render(<App tabs={data}/>, document.body);
In the case of tabs, why would the parent need to control which one is open?
For example the user flow dictates that the user cannot freely jump between the tabs, needs to complete tab1 to move to tab2.
So with the above pattern, the app logic that oversees "everything" can decide wether the user can move to the next tab or not.